diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index fef03f8a62d6e40656be24f4ec402e1135c1eb78..0000000000000000000000000000000000000000 --- a/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 4 diff --git a/.gitignore b/.gitignore index 554c557063fce00e4529110174df802bbc7d456f..37a4eb8b43d978ea05b2c6e3fb934f9b40dddd4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,15 @@ -# built application files -*.apk -*.ap_ - -# files for the dex VM -*.dex - -# Java class files -*.class - -# generated files -bin/ -gen/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Eclipse project files -.classpath -.project - -# Proguard folder generated by Eclipse -proguard/ - -# Intellij project files *.iml -*.ipr -*.iws -.idea/ -crashlytics-build.properties +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +/entry/.preview +.cxx diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index dc175415a44e0eea2f59afaa683c32e8890ae4a0..0000000000000000000000000000000000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "usage2"] - path = usage2 - url = https://github.com/agrosner/DBFlowDocs.git diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..0b757e51ab50ceedd6a81cce8f9645b0de4fbf2d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.0.1-SNAPSHOT +* ohos 第一个版本 + +* 实现了原库大部分功能 diff --git a/README.OPENSOURCE b/README.OPENSOURCE new file mode 100644 index 0000000000000000000000000000000000000000..0b07d21875cf71bed2d6b96ee3be9176effb3da9 --- /dev/null +++ b/README.OPENSOURCE @@ -0,0 +1,10 @@ +[ + { + "Name": "DBFlow", + "License": "MIT License", + "License File": "LICENSE", + "Version Number": "4.2.4", + "Upstream URL": "https://github.com/Raizlabs/DBFlow", + "Description": "DBFlow是一个快速、高效、功能丰富数据库组件,使得使用数据库成为一种乐趣。" + } +] \ No newline at end of file diff --git a/README.md b/README.md index 2f79a2b6e8a48f962bb13cbf9dd509199907276b..15281281929fced8061ae6b000382529cc541973 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,171 @@ -# README +# DBFlow -![Image](https://github.com/agrosner/DBFlow/blob/develop/dbflow_banner.png?raw=true) +#### 项目介绍 +- 项目名称:DBFlow +- 所属系列:openharmony的第三方组件适配移植 +- 功能:DBFlow是一个快速、高效、功能丰富数据库组件,使得使用数据库成为一种乐趣。 +- 项目移植状态:主功能完成 +- 调用差异:无 +- 开发版本:sdk5,DevEco Studio 2.1 Release +- 基线版本:4.2.4 -[![JitPack.io](https://img.shields.io/badge/JitPack.io-5.0.0-alpha2-red.svg?style=flat)](https://jitpack.io/#Raizlabs/DBFlow) [![Android Weekly](http://img.shields.io/badge/Android%20Weekly-%23129-2CB3E5.svg?style=flat)](http://androidweekly.net/issues/issue-129) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-DBFlow-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1134) +#### 安装教程 -DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to generate SQLite boilerplate for you and provides a powerful SQLite query language that makes using SQLite a joy. -DBFlow is built from a collection of some of the best features of many database libraries. Don't let an ORM or library get in your way, let the code you write in your applications be the best as possible. +1.在项目根目录下的build.gradle文件中, -DBFlow Contains: - -**Kotlin:** Built using the language, the library is super-concise, null-safe and efficient. - -**Annotation Processor**: Generates the necessary code that you don't need to write. - -**Core:** Contains the main annotations and misc classes that are shared across all of DBFlow. - -**DBFlow:** The main library artifact used in conjunction with the previous two artifacts. - -**Coroutines:** Adds coroutine support for queries. - -**RX Java:** Enable applications to be reactive by listening to DB changes and ensuring your subscribers are up-to-date. - -**Paging:** Android architecture component paging library support for queries via `QueryDataSource`. - -**LiveData:** Android architecture LiveData support for queries on table changes. - -**SQLCipher:** Easy database encryption support in this library. - -**SQLite Query Language:** Enabling autocompletion on sqlite queries combined with Kotlin language features means SQLite-like syntax. - -## Changelog - -Changes exist in the [releases tab](https://github.com/Raizlabs/DBFlow/releases). - -## Usage Docs - -For more detailed usage, check out it out [here](https://dbflow.gitbook.io/dbflow/) - -## Including in your project - -Add jitpack.io to your project's repositories: - -```groovy -allProjects { - repositories { - google() - // required to find the project's artifacts - // place last - maven { url "https://www.jitpack.io" } - } +``` +allprojects { + repositories { + maven { + url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + } + } } ``` -Add artifacts to your project: - -```groovy - apply plugin: 'kotlin-kapt' // only required for kotlin consumers. +2.在entry模块的build.gradle文件中, - def dbflow_version = "5.0.0-alpha2" - // or 10-digit short-hash of a specific commit. (Useful for bugs fixed in develop, but not in a release yet) - - dependencies { +``` + dependencies { + implementation('com.gitee.chinasoft_ohos:DBFlow:0.0.1-SNAPSHOT') + ...... + } +``` - // Use if Kotlin user. - kapt "com.github.agrosner.dbflow:processor:${dbflow_version}" +在sdk5,DevEco Studio 2.1 Release下项目可直接运行 +如无法运行,删除项目.gradle,.idea,build,gradle,build.gradle文件, +并依据自己的版本创建新项目,将新项目的对应文件复制到根目录下 - // Annotation Processor - // if only using Java, use this. If using Kotlin do NOT use this. - annotationProcessor "com.github.agrosner.dbflow:processor:${dbflow_version}" +#### 使用说明 +* 初始化: + 调用FlowManager.init()方法,传入对应的参数,方法如下: +``` + FlowManager.init(DemoApp.context, builder -> { + DatabaseConfig.Builder builder1 = DatabaseConfig.builder(PrepackagedDB.class, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + builder1.transactionManagerCreator(ImmediateTransactionManager::new); + dbConfigBlock.apply(FlowConfig.builder(DemoApp.context) + .database(builder1.build())); + return null; + }); +``` +* 创建数据库以及数据库表,方法如下: +``` + @Database(version = 1) + public abstract class TestDatabase extends DBFlowDatabase { + @Migration(version = 1, database = TestDatabase.class, priority = 5) + public static class TestMigration extends UpdateTableMigration { + public void onPreMigrate() { + super.onPreMigrate(); + set(SimpleModel_Table.name.eq("Test")).where(SimpleModel_Table.name.eq("Test1")); + } + + public TestMigration() { + super(SimpleTestModels.SimpleModel.class); + } + + } + @Migration(version = 1, database = TestDatabase.class, priority = 1) + public static class SecondMigration extends BaseMigration { + public void migrate(DatabaseWrapper database) { + + } + } + } +``` +* CRUD基本操作: +``` +TestDatabase db= FlowManager.getDatabase(TestDatabase.class); + for (int i = 0; i < 9; i++) { + SimpleTestModels.SimpleModel simpleModel=new SimpleTestModels.SimpleModel("$it"); + Model.save(SimpleTestModels.SimpleModel.class, simpleModel, db); - // core set of libraries - implementation "com.github.agrosner.dbflow:core:${dbflow_version}" - implementation "com.github.agrosner.dbflow:lib:${dbflow_version}" + Model.insert(SimpleTestModels.SimpleModel.class, simpleModel, db); - // sql-cipher database encryption (optional) - implementation "com.github.agrosner.dbflow:sqlcipher:${dbflow_version}" - implementation "net.zetetic:android-database-sqlcipher:${sqlcipher_version}@aar" + Model.delete(SimpleTestModels.SimpleModel.class, simpleModel, db); - // RXJava 2 support - implementation "com.github.agrosner.dbflow:reactive-streams:${dbflow_version}" + Model.update(SimpleTestModels.SimpleModel.class, simpleModel, db); + } +``` +* 事务操作、批量操作: +``` +FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> { + dbFlowDatabase.executeTransaction((ITransaction) databaseWrapper -> { + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test").executeInsert(dbFlowDatabase); + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test1").executeInsert(dbFlowDatabase); + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test2").executeInsert(dbFlowDatabase); + return null; + }); + return null; + }); +``` +``` +DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + + db.beginTransactionAsync((Function) databaseWrapper -> { + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + return null; + }).success((objectTransaction, o) -> { + FlowCursorIterator iterator = SQLite.select().from(SimpleModel.class).cursorList(db).iterator(2, 7); + final int[] count = {0}; + + iterator.forEachRemaining(simpleModel -> { + assertEquals(String.valueOf(count[0] + 2), simpleModel.name); + count[0]++; + }); + assertEquals(7, count[0]); + return null; + }).execute(null, null, null, null); +``` +``` +FlowManager.database(TestDatabase.class, testDatabase -> null) + .beginTransactionAsync((Function) db -> { + for(int i=0; i<=2; i++) { + LiveDataModel model = new LiveDataModel(String.valueOf(i), i); + Model.insert(LiveDataModel.class, model, db); + } + return null; + }).execute(null, null, null, null); +``` +* 释放资源: +``` + FlowManager.destroy(); +``` - // Kotlin Coroutines - implementation "com.github.agrosner.dbflow:coroutines:${dbflow_version}" +#### 测试信息 - // Android Architecture Components Paging Library Support - implementation "com.github.agrosner.dbflow:paging:${dbflow_version}" +CodeCheck代码测试无异常 - // Android Architecture Components LiveData Library Support - implementation "com.github.agrosner.dbflow:livedata:${dbflow_version}" +CloudTest代码测试无异常 - // adds generated content provider annotations + support. - implementation "com.github.agrosner.dbflow:contentprovider:${dbflow_version}" +火绒安全病毒安全检测通过 - } -``` +当前版本demo功能与原组件基本无差异 -## Pull Requests +#### 版本迭代 -I welcome and encourage all pull requests. Here are some basic rules to follow to ensure timely addition of your request: 1. Match coding style \(braces, spacing, etc.\) This is best achieved using **Reformat Code** shortcut, command+option+L on Mac and Ctrl+Alt+L on Windows, with Android Studio defaults. 2. If its a feature, bugfix, or anything please only change code to what you specify. 3. Please keep PR titles easy to read and descriptive of changes, this will make them easier to merge :\) 4. Pull requests _must_ be made against `develop` branch. Any other branch \(unless specified by the maintainers\) will get **rejected**. 5. Have fun! +- 0.0.1-SNAPSHOT -## Maintainer +#### 版权和许可信息 +Copyright (c) 2014 Raizlabs -Originally created by [Raizlabs](https://www.raizlabs.com), a [Rightpoint](https://www.rightpoint.com) company +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: -Maintained by [agrosner](https://github.com/agrosner) \([@agrosner](https://www.twitter.com/agrosner)\) +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/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index e15d019ee3ee9b34be56f673470a9edbfa32cb53..0000000000000000000000000000000000000000 --- a/SUMMARY.md +++ /dev/null @@ -1,31 +0,0 @@ -# Table of contents - -* [README](README.md) -* [Usage Docs](usage2/README.md) - * [Including In Project](usage2/including-in-project.md) - * [Getting Started](usage2/gettingstarted.md) - * [Proguard](usage2/proguard.md) - * [Advanced Usage](usage2/advanced-usage/README.md) - * [Caching](usage2/advanced-usage/caching.md) - * [List Queries](usage2/advanced-usage/listbasedqueries.md) - * [Multiple Modules](usage2/advanced-usage/multiplemodules.md) - * [QueryModels](usage2/advanced-usage/querymodels.md) - * [Indexing](usage2/advanced-usage/indexing.md) - * [SQLCipher](usage2/advanced-usage/sqlciphersupport.md) - * [Main Usage](usage2/usage/README.md) - * [Databases](usage2/usage/databases.md) - * [Models](usage2/usage/models.md) - * [Migrations](usage2/usage/migrations.md) - * [Views](usage2/usage/modelviews.md) - * [Relationships](usage2/usage/relationships.md) - * [Storing Data](usage2/usage/storingdata.md) - * [Retrieval](usage2/usage/retrieval.md) - * [SQLite Query Language](usage2/usage/sqlitewrapperlanguage.md) - * [TypeConverters](usage2/usage/typeconverters.md) - * [Observability](usage2/usage/observability.md) - * [RXJavaSupport](usage2/rxjavasupport.md) - * [ContentProviderGeneration](usage2/contentprovidergeneration.md) - * [5.0 Migration Guide](usage2/5.0-migration-guide.md) - * [Migration4Guide](usage2/migration4guide.md) -* [ISSUE\_TEMPLATE](issue_template.md) - diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..408f9a06fa512528b758669fb40d67b2485e95b5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: 'com.huawei.ohos.app' + +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } +} + +buildscript { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + jcenter() + } + dependencies { + classpath 'com.huawei.ohos:hap:2.4.5.0' + classpath 'com.huawei.ohos:decctest:1.2.4.1' + + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21' + } +} + +allprojects { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + jcenter() + maven { url "https://jitpack.io" } // 添加 + } +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index ce1013f9b6c182f774860b8e496ff2cb713d9faa..0000000000000000000000000000000000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -buildscript { - repositories { - jcenter() - google() - gradlePluginPortal() - } - dependencies { - classpath("com.android.tools.build:gradle:4.1.2") - classpath("com.github.dcendents:android-maven-gradle-plugin:2.1") - classpath("com.getkeepsafe.dexcount:dexcount-gradle-plugin:2.0.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin}") - } -} - -allprojects { - repositories { - jcenter() - google() - maven(url = "https://www.jitpack.io") - } -} - -tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) -} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index c39a297b0fec55d4ae123ae9b3e700cd52c95b35..0000000000000000000000000000000000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - jcenter() -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt deleted file mode 100644 index 994276fd53125ccdcc11f36f22f69915181edb08..0000000000000000000000000000000000000000 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ /dev/null @@ -1,24 +0,0 @@ -object Versions { - const val Kotlin = "1.4.21" - const val TargetSdk = 28 - const val MinSdk = 7 - const val MinSdkRX = 16 - const val SQLCipherMin = 7 - const val ArchMin = 14 -} - -object Dependencies { - const val SqlCipher = "net.zetetic:android-database-sqlcipher:4.4.2" - const val RX = "io.reactivex.rxjava3:rxjava:3.0.4" - const val Coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2" - const val JavaPoet = "com.squareup:javapoet:1.11.1" - const val KPoet = "com.github.agrosner:KPoet:1.0.0" - const val JavaXAnnotation = "org.glassfish:javax.annotation:10.0-b28" - const val JUnit = "junit:junit:4.12" - - object AndroidX { - const val Annotations = "androidx.annotation:annotation:1.1.0" - const val LiveData = "androidx.lifecycle:lifecycle-livedata:2.2.0" - const val Paging = "androidx.paging:paging-runtime:2.1.2" - } -} diff --git a/contentprovider-annotations/build.gradle b/contentprovider-annotations/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..e004e72bd3a00eb944194c6e6cfe2a6e74d940e1 --- /dev/null +++ b/contentprovider-annotations/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java-library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api(project(":core")) +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" diff --git a/contentprovider-annotations/build.gradle.kts b/contentprovider-annotations/build.gradle.kts deleted file mode 100644 index 2429ef14e6a4d9692a0513e2f64fc5fa25684975..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") -} - -// project.ext.artifactId = bt_name - -tasks.withType { - kotlinOptions.jvmTarget = "1.8" -} - -dependencies { - api(project(":core")) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/contentprovider-annotations/gradle.properties b/contentprovider-annotations/gradle.properties deleted file mode 100644 index ec906f2d63b2f0d26c517eb4f8e536b0adbf8e53..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=contentprovider-annotations -bt_packaging=jar -bt_artifact_id=contentprovider-annotations \ No newline at end of file diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentProvider.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2ff3ba05f069890441d184dcd576cd3c672ebe44 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentProvider.java @@ -0,0 +1,32 @@ +package com.dbflow5.contentprovider.annotation; + + +/** + * Description: Defines a Content Provider that gets generated. + */ + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface ContentProvider{ + /** + * @return The authority URI for this provider. + */ + String authority(); + /** + * @return The class of the database this belongs to + */ + Class database(); + /** + * @return The base content uri String to use for all paths + */ + String baseContentUri() default ""; + + /** + * @return the holder class to pass to constructor of the ContentProvider so it will initialize before using DB. + */ + Class initializeHolderClass() default Object.class; + +} diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentType.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentType.java new file mode 100644 index 0000000000000000000000000000000000000000..c7eb26ed8cc5689393ee86b807bb743ebe9f16f2 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentType.java @@ -0,0 +1,7 @@ +package com.dbflow5.contentprovider.annotation; + +public class ContentType { + public static final String VND_MULTIPLE = "vnd.android.cursor.dir/"; + + public static final String VND_SINGLE = "vnd.android.cursor.item/"; +} diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentUri.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentUri.java new file mode 100644 index 0000000000000000000000000000000000000000..1195aaa97a40816adaa3d0b3e4d9d28abd7df490 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/ContentUri.java @@ -0,0 +1,45 @@ +package com.dbflow5.contentprovider.annotation; + +/** + * Description: Defines the URI for a content provider. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value = {ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +public @interface ContentUri{ + /** + * @return the path of this ContentUri. ex: notes/#, notes/1, etc. Must be unique within a [TableEndpoint] + */ + String path(); + /** + * @return The type of content that this uri is associated with. Ex: [ContentType.VND_SINGLE] + */ + String type(); + /** + * @return If the path defines "#", then we use these numbers to find them in the same order as + * where column. + */ + PathSegment[] segments() default {}; + /** + * @return false if you wish to not allow queries from the specified URI. + */ + boolean queryEnabled() default true; + /** + * @return false if you wish to prevent inserts. + */ + boolean insertEnabled() default true; + /** + * @return false if you wish to prevent deletion. + */ + boolean deleteEnabled() default true; + /** + * @return false if you wish to prevent updates. + */ + boolean updateEnabled() default true; +} + diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/Notify.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/Notify.java new file mode 100644 index 0000000000000000000000000000000000000000..5beb7609afcf98878c4a6ce59bd28f173ed66b88 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/Notify.java @@ -0,0 +1,25 @@ +package com.dbflow5.contentprovider.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description: Annotates a method part of [com.dbflow5.annotation.provider.TableEndpoint] + * that gets called back when changed. The method must return a Uri or an array of Uri[] to notify changed on + * the content provider. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface Notify{ + /** + * @return The [com.dbflow5.annotation.provider.Notify.Method] notify + */ + NotifyMethod notifyMethod(); + /** + * @return Registers itself for the following paths. If a specific path is called for the specified + * method, the method this annotation corresponds to will be called. + */ + String[] paths() default {}; +} diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/NotifyMethod.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/NotifyMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..035d7e8c1c3632074cb31e5568d206b5007340f3 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/NotifyMethod.java @@ -0,0 +1,7 @@ +package com.dbflow5.contentprovider.annotation; + +public enum NotifyMethod { + INSERT, + UPDATE, + DELETE +} diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/PathSegment.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/PathSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..d02f0e3e0e4f76e52168162c9bead0478dc519c6 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/PathSegment.java @@ -0,0 +1,12 @@ +package com.dbflow5.contentprovider.annotation; + +public @interface PathSegment { + /** + * @return The number segment this corresponds to. + */ + int segment() default 0; + /** + * @return The column name that this segment will use. + */ + String column() default ""; +} diff --git a/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/TableEndpoint.java b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/TableEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..b2b8bdc973175310f4420b2987d6dd8e637b15e0 --- /dev/null +++ b/contentprovider-annotations/src/main/java/com/dbflow5/contentprovider/annotation/TableEndpoint.java @@ -0,0 +1,20 @@ +package com.dbflow5.contentprovider.annotation; + +import java.lang.annotation.*; + +/** + * Description: Defines an endpoint that gets placed inside of a [ContentProvider] + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface TableEndpoint{ + /** + * @return The name of the table this endpoint corresponds to. + */ + String name(); + /** + * @return When placed in a top-level class, this is required to connect it to a provider. + */ + Class contentProvider(); +} diff --git a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentProvider.kt b/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentProvider.kt deleted file mode 100644 index 3d7b44515c5e764e05979cd1e230787372a7c216..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentProvider.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.dbflow5.contentprovider.annotation - -import kotlin.reflect.KClass - -/** - * Description: Defines a Content Provider that gets generated. - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -@Retention(AnnotationRetention.SOURCE) -annotation class ContentProvider( - /** - * @return The authority URI for this provider. - */ - val authority: String, - /** - * @return The class of the database this belongs to - */ - val database: KClass<*>, - /** - * @return The base content uri String to use for all paths - */ - val baseContentUri: String = "", - - /** - * @return the holder class to pass to constructor of the ContentProvider so it will initialize before using DB. - */ - val initializeHolderClass: KClass<*> = Any::class) diff --git a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentUri.kt b/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentUri.kt deleted file mode 100644 index da0ad2d6a523ca91dea803dd6c9c032667bc3398..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/ContentUri.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.dbflow5.contentprovider.annotation - -/** - * Description: Defines the URI for a content provider. - */ -@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) -@Retention(AnnotationRetention.SOURCE) -annotation class ContentUri( - /** - * @return the path of this ContentUri. ex: notes/#, notes/1, etc. Must be unique within a [TableEndpoint] - */ - val path: String, - /** - * @return The type of content that this uri is associated with. Ex: [ContentType.VND_SINGLE] - */ - val type: String, - /** - * @return If the path defines "#", then we use these numbers to find them in the same order as - * where column. - */ - val segments: Array = [], - /** - * @return false if you wish to not allow queries from the specified URI. - */ - val queryEnabled: Boolean = true, - /** - * @return false if you wish to prevent inserts. - */ - val insertEnabled: Boolean = true, - /** - * @return false if you wish to prevent deletion. - */ - val deleteEnabled: Boolean = true, - /** - * @return false if you wish to prevent updates. - */ - val updateEnabled: Boolean = true) - -/** - * Provides some handy constants for defining a [.type] - */ -object ContentType { - - const val VND_MULTIPLE = "vnd.android.cursor.dir/" - - const val VND_SINGLE = "vnd.android.cursor.item/" -} - -/** - * Defines the path segment that we use when we specify "#" in the path. - */ -annotation class PathSegment( - /** - * @return The number segment this corresponds to. - */ - val segment: Int, - /** - * @return The column name that this segment will use. - */ - val column: String) \ No newline at end of file diff --git a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/Notify.kt b/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/Notify.kt deleted file mode 100644 index fc6c0d257dd72878de3cfe6479352a4614d09257..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/Notify.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.dbflow5.contentprovider.annotation - -/** - * Description: Annotates a method part of [com.dbflow5.annotation.provider.TableEndpoint] - * that gets called back when changed. The method must return a Uri or an array of Uri[] to notify changed on - * the content provider. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) -annotation class Notify( - /** - * @return The [com.dbflow5.annotation.provider.Notify.Method] notify - */ - val notifyMethod: NotifyMethod, - /** - * @return Registers itself for the following paths. If a specific path is called for the specified - * method, the method this annotation corresponds to will be called. - */ - val paths: Array = []) - -enum class NotifyMethod { - INSERT, - UPDATE, - DELETE -} \ No newline at end of file diff --git a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/TableEndpoint.kt b/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/TableEndpoint.kt deleted file mode 100644 index cc3b48b2442d72b78efdb8c6b4c9521b25b8c4f7..0000000000000000000000000000000000000000 --- a/contentprovider-annotations/src/main/kotlin/com/dbflow5/contentprovider/annotation/TableEndpoint.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.dbflow5.contentprovider.annotation - -import kotlin.reflect.KClass - -/** - * Description: Defines an endpoint that gets placed inside of a [ContentProvider] - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -@Retention(AnnotationRetention.SOURCE) -annotation class TableEndpoint( - /** - * @return The name of the table this endpoint corresponds to. - */ - val name: String, - /** - * @return When placed in a top-level class, this is required to connect it to a provider. - */ - val contentProvider: KClass<*>) diff --git a/contentprovider/build.gradle b/contentprovider/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..e898ecd6851ae0ae8137d306e1d5b51dd4ef5c93 --- /dev/null +++ b/contentprovider/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + api(project(":lib")) + api(project(":contentprovider-annotations")) +} diff --git a/contentprovider/build.gradle.kts b/contentprovider/build.gradle.kts deleted file mode 100644 index d57fb5762fb125c5fe7b83b955bb22563f83c4fb..0000000000000000000000000000000000000000 --- a/contentprovider/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} - -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.MinSdk) - targetSdkVersion(Versions.TargetSdk) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - api(project(":lib")) - api(project(":contentprovider-annotations")) - api(Dependencies.AndroidX.Annotations) -} - -apply(from = "../kotlin-artifacts.gradle") - diff --git a/contentprovider/consumer-rules.pro b/contentprovider/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/contentprovider/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/contentprovider/gradle.properties b/contentprovider/gradle.properties deleted file mode 100644 index 1802744c1acb8c64346e36739e2af31932026267..0000000000000000000000000000000000000000 --- a/contentprovider/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=contentprovider -bt_packaging=aar -bt_artifact_id=contentprovider \ No newline at end of file diff --git a/contentprovider/proguard-rules.pro b/contentprovider/proguard-rules.pro index f1b424510da51fd82143bc74a0a801ae5a1e2fcd..f7666e47561d514b2a76d5a7dfbb43ede86da92a 100644 --- a/contentprovider/proguard-rules.pro +++ b/contentprovider/proguard-rules.pro @@ -1,21 +1 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/contentprovider/src/main/AndroidManifest.xml b/contentprovider/src/main/AndroidManifest.xml deleted file mode 100644 index 2f5f61755289ca1cfaacdadfc79b1467d08d333d..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/contentprovider/src/main/config.json b/contentprovider/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..4fabe65763fae8909325597749268543690628b5 --- /dev/null +++ b/contentprovider/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.provider", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "contentprovider", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/contentprovider/src/main/java/com/dbflow5/provider/BaseContentProvider.java b/contentprovider/src/main/java/com/dbflow5/provider/BaseContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..5f32959704c288816f5eccc2456eba524ffbff07 --- /dev/null +++ b/contentprovider/src/main/java/com/dbflow5/provider/BaseContentProvider.java @@ -0,0 +1,50 @@ +package com.dbflow5.provider; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.transaction.ITransaction; +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.app.Context; +import ohos.data.rdb.ValuesBucket; +import ohos.utils.net.Uri; + +public abstract class BaseContentProvider { + private final DataAbilityHelper mDataAbilityHelper; + protected Class moduleClass; + + protected DBFlowDatabase database = FlowManager.getDatabase(databaseName()); + + protected abstract String databaseName(); + + public BaseContentProvider(Context context, Class databaseHolderClass) { + mDataAbilityHelper = DataAbilityHelper.creator(context); + moduleClass = databaseHolderClass; + + if(moduleClass != null){ + FlowManager.initModule(moduleClass); + }else { + if(context != null) { + FlowManager.init(context, builder -> null); + } + } + } + + public int batchInsert(Uri uri, ValuesBucket[] values) { + int[] count = database.executeTransaction((ITransaction) databaseWrapper -> { + int[] count1 = new int[]{0}; + for (ValuesBucket contentValues : values) { + count1[0] += bulkInsert(uri, contentValues); + } + return count1; + }); + + if(mDataAbilityHelper != null) { + mDataAbilityHelper.notifyChange(uri); + } + return count[0]; + } + + protected abstract int bulkInsert(Uri uri, ValuesBucket contentValues); + +} diff --git a/contentprovider/src/main/java/com/dbflow5/provider/BaseProviderModel.java b/contentprovider/src/main/java/com/dbflow5/provider/BaseProviderModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a31652b0ab62ef77b0d8b5d022535eb342a57884 --- /dev/null +++ b/contentprovider/src/main/java/com/dbflow5/provider/BaseProviderModel.java @@ -0,0 +1,75 @@ +package com.dbflow5.provider; + +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.structure.BaseModel; +import ohos.aafwk.ability.DataAbilityRemoteException; + +/** + * Description: Provides a base implementation of a [Model] backed + * by a content provider. All model operations are overridden using the [ContentUtils]. + * Consider using a [BaseSyncableProviderModel] if you wish to + * keep modifications locally from the [ContentProvider] + */ +public abstract class BaseProviderModel extends BaseModel implements ModelProvider { + + public BaseProviderModel() { + super(); + } + + @Override + public boolean save(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + int count = ContentUtils.update(FlowManager.contentResolver(), updateUri(), this); + if (count == 0) { + return insert(wrapper) > 0; + } else { + return count > 0; + } + } + + @Override + public boolean delete(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + return ContentUtils.delete(FlowManager.contentResolver(), deleteUri(), this) > 0; + } + + @Override + public boolean update(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + return ContentUtils.update(FlowManager.contentResolver(), updateUri(), this) > 0; + } + + @Override + public long insert(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + return ContentUtils.insert(FlowManager.contentResolver(), insertUri(), this); + } + + @Override + public boolean exists(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + FlowCursor cursor = ContentUtils.query(FlowManager.contentResolver(), + queryUri(), modelAdapter.getPrimaryConditionClause(this), ""); + boolean exists = cursor != null && cursor.getRowCount() > 0; + if(cursor != null) { + cursor.close(); + } + return exists; + } + + @Override + public T load(OperatorGroup whereOperatorGroup, String orderBy, DatabaseWrapper wrapper, String... columns) throws DataAbilityRemoteException { + FlowCursor cursor = ContentUtils.query(FlowManager.contentResolver(), + queryUri(), whereOperatorGroup, orderBy, columns); + + if(cursor != null) { + if (cursor.goToFirstRow()) { + return (T)modelAdapter.loadFromCursor(cursor, wrapper); + } + } + return null; + } + + @Override + public T load(DatabaseWrapper databaseWrapper) throws DataAbilityRemoteException { + return (T)load(modelAdapter.getPrimaryConditionClause(this), "", databaseWrapper); + } +} diff --git a/contentprovider/src/main/java/com/dbflow5/provider/BaseSyncableProviderModel.java b/contentprovider/src/main/java/com/dbflow5/provider/BaseSyncableProviderModel.java new file mode 100644 index 0000000000000000000000000000000000000000..1610442f37bd5da02414e0b65263f370bdc83831 --- /dev/null +++ b/contentprovider/src/main/java/com/dbflow5/provider/BaseSyncableProviderModel.java @@ -0,0 +1,65 @@ +package com.dbflow5.provider; + +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.structure.BaseModel; +import ohos.aafwk.ability.DataAbilityRemoteException; + +/** + * Description: Provides a base implementation of a [Model] backed + * by a content provider. All operations sync with the content provider in this app from a [ContentProvider] + */ +public abstract class BaseSyncableProviderModel extends BaseModel implements ModelProvider { + public BaseSyncableProviderModel() { + super(); + } + + @Override + public boolean save(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + boolean isSuccess; + if (exists(wrapper)) { + isSuccess = ContentUtils.update(FlowManager.contentResolver(), updateUri(), this) > 0; + } else { + isSuccess = ContentUtils.insert(FlowManager.contentResolver(), insertUri(), this) > 0; + } + return super.save(wrapper) && isSuccess; + } + + @Override + public boolean delete(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + return super.delete(wrapper) && ContentUtils.delete(FlowManager.contentResolver(), deleteUri(), this) > 0; + } + + @Override + public boolean update(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + return super.update(wrapper) && ContentUtils.update(FlowManager.contentResolver(), updateUri(), this) > 0; + } + + @Override + public long insert(DatabaseWrapper wrapper) throws DataAbilityRemoteException { + long rowId = super.insert(wrapper); + ContentUtils.insert(FlowManager.contentResolver(), insertUri(), this); + return rowId; + } + + @Override + public T load(DatabaseWrapper databaseWrapper) throws DataAbilityRemoteException { + return (T)load(modelAdapter.getPrimaryConditionClause(this), "", databaseWrapper); + } + + @Override + public T load(OperatorGroup whereOperatorGroup, String orderBy, DatabaseWrapper wrapper, String... columns) throws DataAbilityRemoteException { + FlowCursor cursor = ContentUtils.query(FlowManager.contentResolver(), + queryUri(), whereOperatorGroup, orderBy, columns); + if (cursor != null) { + if (cursor.goToFirstRow()) { + T model = (T)modelAdapter.loadFromCursor(cursor, wrapper); + cursor.close(); + return model; + } + } + return null; + } +} diff --git a/contentprovider/src/main/java/com/dbflow5/provider/ContentProviderDatabase.java b/contentprovider/src/main/java/com/dbflow5/provider/ContentProviderDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..ede69b3f97412e66f938c70db497898ecf1a2216 --- /dev/null +++ b/contentprovider/src/main/java/com/dbflow5/provider/ContentProviderDatabase.java @@ -0,0 +1,34 @@ +package com.dbflow5.provider; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.OhosDatabaseWrapper; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.ValuesBucket; + +/** + * Description: Base class providing [ContentValues] for a [DBFlowDatabase]. + */ +public abstract class ContentProviderDatabase extends DBFlowDatabase implements OhosDatabaseWrapper { + + public ContentProviderDatabase() { + super(); + } + + private OhosDatabaseWrapper database() { + if(getWritableDatabase() instanceof OhosDatabaseWrapper) { + return (OhosDatabaseWrapper)getWritableDatabase(); + } + + throw new IllegalStateException("Invalid DB type used. It must be a type of "+OhosDatabaseWrapper.class+". "); + } + + @Override + public long updateWithOnConflict(String tableName, ValuesBucket contentValues, String[] whereColumn, String[] whereValues, RdbStore.ConflictResolution conflictAlgorithm) { + return database().updateWithOnConflict(tableName, contentValues, whereColumn, whereValues, conflictAlgorithm); + } + + @Override + public long insertWithOnConflict(String tableName, String nullColumnHack, ValuesBucket values, RdbStore.ConflictResolution sqLiteDatabaseAlgorithmInt) { + return database().insertWithOnConflict(tableName, nullColumnHack, values, sqLiteDatabaseAlgorithmInt); + } +} \ No newline at end of file diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/ContentUtils.kt b/contentprovider/src/main/java/com/dbflow5/provider/ContentUtils.java similarity index 51% rename from contentprovider/src/main/kotlin/com/dbflow5/provider/ContentUtils.kt rename to contentprovider/src/main/java/com/dbflow5/provider/ContentUtils.java index 277e0bf9665044513c9265acb8c38a540bbfaf3c..9cbb377a83701e1ef597df3ae4a0a5414ccf1581 100644 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/ContentUtils.kt +++ b/contentprovider/src/main/java/com/dbflow5/provider/ContentUtils.java @@ -1,28 +1,30 @@ -package com.dbflow5.provider +package com.dbflow5.provider; -import android.annotation.SuppressLint -import android.content.ContentResolver -import android.content.ContentValues -import android.content.Context -import android.net.Uri -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.config.FlowLog -import com.dbflow5.config.modelAdapter -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.Operator -import com.dbflow5.query.OperatorGroup +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.app.Context; +import ohos.data.dataability.DataAbilityPredicates; +import ohos.data.rdb.ValuesBucket; +import ohos.data.resultset.ResultSet; +import ohos.utils.net.Uri; + +import java.util.ArrayList; +import java.util.List; /** * Description: Provides handy wrapper mechanisms for [android.content.ContentProvider] */ -object ContentUtils { - +public class ContentUtils { /** * The default content URI that Android recommends. Not necessary, however. */ - const val BASE_CONTENT_URI = "content://" + public static final String BASE_CONTENT_URI = "content://"; /** * Constructs an Uri with the [.BASE_CONTENT_URI] and authority. Add paths to append to the Uri. @@ -31,9 +33,9 @@ object ContentUtils { * @param paths The list of paths to append. * @return A complete Uri for a [ContentProvider] */ - @JvmStatic - fun buildUriWithAuthority(authority: String, vararg paths: String): Uri = - buildUri(BASE_CONTENT_URI, authority, *paths) + public static Uri buildUriWithAuthority(String authority, String... paths) { + return buildUri(BASE_CONTENT_URI, authority, paths); + } /** * Constructs an Uri with the specified baseContent uri and authority. Add paths to append to the Uri. @@ -43,13 +45,12 @@ object ContentUtils { * @param paths The list of paths to append. * @return A complete Uri for a [ContentProvider] */ - @JvmStatic - fun buildUri(baseContentUri: String, authority: String, vararg paths: String): Uri { - val builder = Uri.parse(baseContentUri + authority).buildUpon() - for (path in paths) { - builder.appendPath(path) + public static Uri buildUri(String baseContentUri, String authority, String... paths) { + Uri.Builder builder = Uri.parse(baseContentUri + authority).makeBuilder(); + for (String path : paths) { + builder.appendDecodedPath(path); } - return builder.build() + return builder.build(); } /** @@ -60,9 +61,9 @@ object ContentUtils { * @param model The model to insert. * @return A Uri of the inserted data. */ - @JvmStatic - fun insert(context: Context, insertUri: Uri, model: TableClass): Uri? = - insert(context.contentResolver, insertUri, model) + public static int insert(Context context, Uri insertUri, TableClass model) throws DataAbilityRemoteException { + return insert(DataAbilityHelper.creator(context), insertUri, model); + } /** * Inserts the model into the [android.content.ContentResolver]. Uses the insertUri to resolve @@ -73,17 +74,16 @@ object ContentUtils { * @param model The model to insert. * @return The Uri of the inserted data. */ - @JvmStatic - fun insert(contentResolver: ContentResolver, insertUri: Uri, model: TableClass): Uri? { - val adapter = model.javaClass.modelAdapter + public static int insert(DataAbilityHelper contentResolver, Uri insertUri, TableClass model) throws DataAbilityRemoteException { + ModelAdapter adapter = FlowManager.modelAdapter((Class) model.getClass()); - val contentValues = ContentValues() - adapter.bindToInsertValues(contentValues, model) - val uri: Uri? = contentResolver.insert(insertUri, contentValues) - uri?.let { - adapter.updateAutoIncrement(model, uri.pathSegments[uri.pathSegments.size - 1].toLong()) + ValuesBucket contentValues = new ValuesBucket(); + adapter.bindToInsertValues(contentValues, model); + int index = contentResolver.insert(insertUri, contentValues); + if(index >= 0) { + adapter.updateAutoIncrement(model, index); } - return uri + return index; } /** @@ -98,20 +98,18 @@ object ContentUtils { * @param models The models to insert. * @return The count of the rows affected by the insert. */ - @JvmStatic - fun bulkInsert(contentResolver: ContentResolver, bulkInsertUri: Uri, - table: Class, models: List?): Int { - val contentValues = arrayListOf() - val adapter = table.modelAdapter + public static int bulkInsert(DataAbilityHelper contentResolver, Uri bulkInsertUri, Class table, List models) throws DataAbilityRemoteException { + List contentValues = new ArrayList<>(); + ModelAdapter adapter = FlowManager.modelAdapter(table); if (models != null) { - for (i in contentValues.indices) { - val values = ContentValues() - adapter.bindToInsertValues(values, models[i]) - contentValues += values + for (TableClass model : models) { + ValuesBucket values = new ValuesBucket(); + adapter.bindToInsertValues(values, model); + contentValues.add(values); } } - return contentResolver.bulkInsert(bulkInsertUri, contentValues.toTypedArray()) + return contentResolver.batchInsert(bulkInsertUri, (ValuesBucket[]) contentValues.toArray()); } /** @@ -125,12 +123,9 @@ object ContentUtils { * @param models The models to insert. * @return The count of the rows affected by the insert. */ - @JvmStatic - fun bulkInsert(context: Context, - bulkInsertUri: Uri, - table: Class, - models: List): Int = - bulkInsert(context.contentResolver, bulkInsertUri, table, models) + public static int bulkInsert(Context context, Uri bulkInsertUri, Class table, List models) throws DataAbilityRemoteException { + return bulkInsert(DataAbilityHelper.creator(context), bulkInsertUri, table, models); + } /** * Updates the model through the [android.content.ContentResolver]. Uses the updateUri to @@ -140,11 +135,9 @@ object ContentUtils { * @param model A model to update * @return The number of rows updated. */ - @JvmStatic - fun update(context: Context, - updateUri: Uri, - model: TableClass): Int = - update(context.contentResolver, updateUri, model) + public static int update(Context context, Uri updateUri, TableClass model) throws DataAbilityRemoteException { + return update(DataAbilityHelper.creator(context), updateUri, model); + } /** * Updates the model through the [android.content.ContentResolver]. Uses the updateUri to @@ -155,19 +148,20 @@ object ContentUtils { * @param model The model to update * @return The number of rows updated. */ - @JvmStatic - fun update(contentResolver: ContentResolver, - updateUri: Uri, model: TableClass): Int { - val adapter = model.javaClass.modelAdapter + public static int update(DataAbilityHelper contentResolver, Uri updateUri, TableClass model) throws DataAbilityRemoteException { + ModelAdapter adapter = FlowManager.modelAdapter((Class)model.getClass()); + + ValuesBucket contentValues = new ValuesBucket(); + adapter.bindToContentValues(contentValues, model); - val contentValues = ContentValues() - adapter.bindToContentValues(contentValues, model) - val count = contentResolver.update(updateUri, contentValues, - adapter.getPrimaryConditionClause(model).query, null) + String sql = adapter.getPrimaryConditionClause(model).getQuery(); + DataAbilityPredicates predicates = new DataAbilityPredicates(); + // TODO sql->predicates + int count = contentResolver.update(updateUri, contentValues, predicates); if (count == 0) { - FlowLog.log(FlowLog.Level.W, "Updated failed of: " + model.javaClass) + FlowLog.log(FlowLog.Level.W, "Updated failed of: " + model.getClass()); } - return count + return count; } /** @@ -178,9 +172,9 @@ object ContentUtils { * @param model The model to delete * @return The number of rows deleted. */ - @JvmStatic - fun delete(context: Context, deleteUri: Uri, model: TableClass): Int = - delete(context.contentResolver, deleteUri, model) + public static int delete(Context context, Uri deleteUri, TableClass model) throws DataAbilityRemoteException { + return delete(DataAbilityHelper.creator(context), deleteUri, model); + } /** * Deletes the specified model through the [android.content.ContentResolver]. Uses the deleteUri @@ -191,19 +185,21 @@ object ContentUtils { * @param model The model to delete * @return The number of rows deleted. */ - @JvmStatic - fun delete(contentResolver: ContentResolver, deleteUri: Uri, model: TableClass): Int { - val adapter = model.javaClass.modelAdapter + public static int delete(DataAbilityHelper contentResolver, Uri deleteUri, TableClass model) throws DataAbilityRemoteException { + ModelAdapter adapter = FlowManager.modelAdapter((Class)model.getClass()); - val count = contentResolver.delete(deleteUri, adapter.getPrimaryConditionClause(model).query, null) + String sql = adapter.getPrimaryConditionClause(model).getQuery(); + DataAbilityPredicates predicates = new DataAbilityPredicates(); + //TODO 转换sql->predicates + int count = contentResolver.delete(deleteUri, predicates); // reset autoincrement to 0 if (count > 0) { - adapter.updateAutoIncrement(model, 0) + adapter.updateAutoIncrement(model, 0); } else { - FlowLog.log(FlowLog.Level.W, "A delete on ${model.javaClass} within the ContentResolver appeared to fail.") + FlowLog.log(FlowLog.Level.W, "A delete on ${model.javaClass} within the ContentResolver appeared to fail."); } - return count + return count; } /** @@ -217,12 +213,16 @@ object ContentUtils { * @param columns The list of columns to query. * @return A [android.database.Cursor] */ - @JvmStatic - fun query(contentResolver: ContentResolver, queryUri: Uri, - whereConditions: OperatorGroup, - orderBy: String?, vararg columns: String?): FlowCursor? = - contentResolver.query(queryUri, columns, whereConditions.query, null, orderBy) - ?.let { cursor -> FlowCursor.from(cursor) } + public static FlowCursor query(DataAbilityHelper contentResolver, Uri queryUri, OperatorGroup whereConditions, String orderBy, String... columns) throws DataAbilityRemoteException { + String sql = whereConditions.getQuery(); + DataAbilityPredicates predicates = new DataAbilityPredicates(); + predicates.orderByDesc(orderBy); + ResultSet cursor = contentResolver.query(queryUri, columns, predicates); + if(cursor != null) { + return FlowCursor.from(cursor); + } + return null; + } /** * Queries the [android.content.ContentResolver] with the specified queryUri. It will generate @@ -235,15 +235,12 @@ object ContentUtils { * @param columns The list of columns to query. * @return A list of [TableClass] */ - @JvmStatic - fun queryList(context: Context, - queryUri: Uri, table: Class, - databaseWrapper: DatabaseWrapper, - whereConditions: OperatorGroup, - orderBy: String, vararg columns: String): List? = - queryList(context.contentResolver, queryUri, table, - databaseWrapper, whereConditions, orderBy, *columns) - + public static List queryList(Context context, Uri queryUri, Class table, + DatabaseWrapper databaseWrapper, OperatorGroup whereConditions, + String orderBy, String... columns) throws DataAbilityRemoteException { + return queryList(DataAbilityHelper.creator(context), queryUri, table, + databaseWrapper, whereConditions, orderBy, columns); + } /** * Queries the [android.content.ContentResolver] with the specified queryUri. It will generate @@ -257,21 +254,23 @@ object ContentUtils { * @param columns The list of columns to query. * @return A list of [TableClass] */ - @SuppressLint("Recycle") - @JvmStatic - fun queryList(contentResolver: ContentResolver, queryUri: Uri, - table: Class, - databaseWrapper: DatabaseWrapper, - whereConditions: OperatorGroup, - orderBy: String, vararg columns: String): List? { - val cursor = contentResolver.query(queryUri, columns, whereConditions.query, null, orderBy)?.let { cursor -> - FlowCursor.from(cursor) - } - return cursor.use { c -> - table.modelAdapter - .listModelLoader - .load(c, databaseWrapper) + public static List queryList(DataAbilityHelper contentResolver, Uri queryUri, Class table, + DatabaseWrapper databaseWrapper, OperatorGroup whereConditions, + String orderBy, String... columns) throws DataAbilityRemoteException { + + String sql = whereConditions.getQuery(); + DataAbilityPredicates predicates = new DataAbilityPredicates(); + predicates.orderByDesc(orderBy); + //TODO sql->predicates + ResultSet cursor = contentResolver.query(queryUri, columns, predicates); + FlowCursor flowCursor = null; + if(cursor != null){ + flowCursor = FlowCursor.from(cursor); } + + return FlowManager.modelAdapter(table) + .getListModelLoader() + .load(flowCursor, databaseWrapper); } /** @@ -285,14 +284,12 @@ object ContentUtils { * @param columns The list of columns to query. * @return The first [TableClass] of the list query from the content provider. */ - @JvmStatic - fun querySingle(context: Context, - queryUri: Uri, table: Class, - databaseWrapper: DatabaseWrapper, - whereConditions: OperatorGroup, - orderBy: String, vararg columns: String): TableClass? = - querySingle(context.contentResolver, queryUri, table, - databaseWrapper, whereConditions, orderBy, *columns) + public static TableClass querySingle(Context context, Uri queryUri, Class table, + DatabaseWrapper databaseWrapper, OperatorGroup whereConditions, + String orderBy, String... columns) throws DataAbilityRemoteException { + return querySingle(DataAbilityHelper.creator(context), queryUri, table, + databaseWrapper, whereConditions, orderBy, columns); + } /** * Queries the [android.content.ContentResolver] with the specified queryUri. It will generate @@ -306,15 +303,13 @@ object ContentUtils { * @param columns The list of columns to query. * @return The first [TableClass] of the list query from the content provider. */ - @JvmStatic - fun querySingle(contentResolver: ContentResolver, - queryUri: Uri, table: Class, - databaseWrapper: DatabaseWrapper, - whereConditions: OperatorGroup, - orderBy: String, vararg columns: String): TableClass? { - val list = queryList(contentResolver, queryUri, table, - databaseWrapper, whereConditions, orderBy, *columns) - return list?.let { if (list.isNotEmpty()) list[0] else null } + public static TableClass querySingle(DataAbilityHelper contentResolver, Uri queryUri, Class table, + DatabaseWrapper databaseWrapper, OperatorGroup whereConditions, + String orderBy, String... columns) throws DataAbilityRemoteException { + List list = queryList(contentResolver, queryUri, table, databaseWrapper, whereConditions, orderBy, columns); + if(list != null){ + return !list.isEmpty()? list.get(0) : null; + } + return null; } - } diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/ModelProvider.kt b/contentprovider/src/main/java/com/dbflow5/provider/ModelProvider.java similarity index 64% rename from contentprovider/src/main/kotlin/com/dbflow5/provider/ModelProvider.kt rename to contentprovider/src/main/java/com/dbflow5/provider/ModelProvider.java index d7b5a1606070311d9ce4ab964ec8819c5beacbe1..cecc79b40da2a32603693c3e6a860caf4fa5a338 100644 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/ModelProvider.kt +++ b/contentprovider/src/main/java/com/dbflow5/provider/ModelProvider.java @@ -1,35 +1,35 @@ -package com.dbflow5.provider +package com.dbflow5.provider; -import android.content.ContentResolver -import android.net.Uri -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.Operator -import com.dbflow5.query.OperatorGroup +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.Operator; +import com.dbflow5.query.OperatorGroup; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.utils.net.Uri; /** * Description: A base interface for Models that are connected to providers. */ -interface ModelProvider { +public interface ModelProvider { /** * @return The [android.net.Uri] that passes to a [android.content.ContentProvider] to delete a Model. */ - val deleteUri: Uri + Uri deleteUri(); /** * @return The [android.net.Uri] that passes to a [android.content.ContentProvider] to insert a Model. */ - val insertUri: Uri + Uri insertUri(); /** * @return The [android.net.Uri] that passes to a [android.content.ContentProvider] to update a Model. */ - val updateUri: Uri + Uri updateUri(); /** * @return The [android.net.Uri] that passes to a [android.content.ContentProvider] to query a Model. */ - val queryUri: Uri + Uri queryUri(); /** * Queries the [ContentResolver] of the app based on the passed parameters and @@ -38,15 +38,16 @@ interface ModelProvider { * @param whereOperatorGroup The set of [Operator] to filter the query by. * @param orderBy The order by without the ORDER BY * @param columns The list of columns to select. Leave blank for * + * @return T */ - fun load(whereOperatorGroup: OperatorGroup, - orderBy: String?, - wrapper: DatabaseWrapper, - vararg columns: String?): T? + T load(OperatorGroup whereOperatorGroup, String orderBy, DatabaseWrapper wrapper, String... columns) throws DataAbilityRemoteException; /** * Queries the [ContentResolver] of the app based on the primary keys of the object and returns a * new object with the first row from the returned data. + * + * @param wrapper DatabaseWrapper + * @return T */ - fun load(wrapper: DatabaseWrapper): T? + T load(DatabaseWrapper wrapper) throws DataAbilityRemoteException; } diff --git a/contentprovider/src/main/java/com/dbflow5/provider/StubContentProvider.java b/contentprovider/src/main/java/com/dbflow5/provider/StubContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c2af5a57e000abc1efe92294a7aef6996149cff9 --- /dev/null +++ b/contentprovider/src/main/java/com/dbflow5/provider/StubContentProvider.java @@ -0,0 +1,39 @@ +package com.dbflow5.provider; + +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.app.Context; +import ohos.data.dataability.DataAbilityPredicates; +import ohos.data.rdb.ValuesBucket; +import ohos.data.resultset.ResultSet; +import ohos.utils.net.Uri; + +/** + * Description: Used as a stub, include this in order to work around Android O changes to [ContentProvider] + */ +public class StubContentProvider { + private DataAbilityHelper mDataAbilityHelper; + + public StubContentProvider(Context context) { + mDataAbilityHelper = DataAbilityHelper.creator(context); + } + + public int insert(Uri uri, ValuesBucket value) { + return -1; + } + + public int delete(Uri uri, DataAbilityPredicates predicates) { + return 0; + } + + public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) { + return 0; + } + + public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) { + return null; + } + + public String getType(Uri uri) { + return null; + } +} diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseContentProvider.kt b/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseContentProvider.kt deleted file mode 100644 index 320926b610e97ee101eb545ff2fa285df4d8ca5f..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseContentProvider.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentProvider -import android.content.ContentValues -import android.net.Uri -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.DatabaseHolder -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.transaction.ITransaction - -/** - * Description: The base provider class that [com.dbflow5.annotation.provider.ContentProvider] - * extend when generated. - */ -abstract class BaseContentProvider -protected constructor(databaseHolderClass: Class? = null) : ContentProvider() { - - protected open var moduleClass: Class? = databaseHolderClass - - protected val database: DBFlowDatabase by lazy { FlowManager.getDatabase(databaseName) } - - protected abstract val databaseName: String - - override fun onCreate(): Boolean { - // If this is a module, then we need to initialize the module as part - // of the creation process. We can assume the framework has been initialized. - moduleClass - ?.let { FlowManager.initModule(it) } - ?: context?.let { FlowManager.init(it) } - return true - } - - override fun bulkInsert(uri: Uri, values: Array): Int { - - val count = database.executeTransaction(object : ITransaction { - override fun execute(databaseWrapper: DatabaseWrapper): IntArray { - val count = intArrayOf(0) - for (contentValues in values) { - count[0] += bulkInsert(uri, contentValues) - } - return count - } - }) - - context?.contentResolver?.notifyChange(uri, null) - return count[0] - } - - protected abstract fun bulkInsert(uri: Uri, contentValues: ContentValues): Int - -} diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseProviderModel.kt b/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseProviderModel.kt deleted file mode 100644 index 9d89a231523d7015e295fab8108d958c5161bdd1..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseProviderModel.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentProvider -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.OperatorGroup -import com.dbflow5.structure.BaseModel -import com.dbflow5.structure.Model - -/** - * Description: Provides a base implementation of a [Model] backed - * by a content provider. All model operations are overridden using the [ContentUtils]. - * Consider using a [BaseSyncableProviderModel] if you wish to - * keep modifications locally from the [ContentProvider] - */ -abstract class BaseProviderModel : BaseModel(), ModelProvider { - - override fun delete(wrapper: DatabaseWrapper): Boolean = ContentUtils.delete(FlowManager.contentResolver, deleteUri, this) > 0 - - override fun save(wrapper: DatabaseWrapper): Boolean { - val count = ContentUtils.update(FlowManager.contentResolver, updateUri, this) - return if (count == 0) { - insert(wrapper) > 0 - } else { - count > 0 - } - } - - override fun update(wrapper: DatabaseWrapper): Boolean = ContentUtils.update(FlowManager.contentResolver, updateUri, this) > 0 - - override fun insert(wrapper: DatabaseWrapper): Long = if (ContentUtils.insert(FlowManager.contentResolver, insertUri, this) != null) 1 else 0 - - /** - * Runs a query on the [ContentProvider] to see if it returns data. - * - * @return true if this model exists in the [ContentProvider] based on its primary keys. - */ - override fun exists(wrapper: DatabaseWrapper): Boolean { - val cursor = ContentUtils.query(FlowManager.contentResolver, - queryUri, modelAdapter.getPrimaryConditionClause(this), "") - val exists = cursor != null && cursor.count > 0 - cursor?.close() - return exists - } - - @Suppress("UNCHECKED_CAST") - override fun load(whereOperatorGroup: OperatorGroup, - orderBy: String?, - wrapper: DatabaseWrapper, - vararg columns: String?): T? { - ContentUtils.query(FlowManager.contentResolver, - queryUri, whereOperatorGroup, orderBy, *columns)?.use { cursor -> - if (cursor.moveToFirst()) { - return modelAdapter.loadFromCursor(cursor, wrapper) as T - } - } - return null - } - - @Suppress("UNCHECKED_CAST") - override fun load(wrapper: DatabaseWrapper): T? = load(modelAdapter.getPrimaryConditionClause(this), "", wrapper) as T? - -} diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseSyncableProviderModel.kt b/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseSyncableProviderModel.kt deleted file mode 100644 index 3fa432fa63ffa82b443869ee989e0e3634d0463b..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/BaseSyncableProviderModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentProvider -import com.dbflow5.config.FlowManager.contentResolver -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.OperatorGroup -import com.dbflow5.structure.BaseModel -import com.dbflow5.structure.Model - -/** - * Description: Provides a base implementation of a [Model] backed - * by a content provider. All operations sync with the content provider in this app from a [ContentProvider] - */ -abstract class BaseSyncableProviderModel : BaseModel(), ModelProvider { - - override fun insert(wrapper: DatabaseWrapper): Long { - val rowId = super.insert(wrapper) - ContentUtils.insert(contentResolver, insertUri, this) - return rowId - } - - override fun save(wrapper: DatabaseWrapper): Boolean = super.save(wrapper) && if (exists(wrapper)) { - ContentUtils.update(contentResolver, updateUri, this) > 0 - } else { - ContentUtils.insert(contentResolver, insertUri, this) != null - } - - override fun delete(wrapper: DatabaseWrapper): Boolean = super.delete(wrapper) && ContentUtils.delete(contentResolver, deleteUri, this) > 0 - - override fun update(wrapper: DatabaseWrapper): Boolean = super.update(wrapper) && ContentUtils.update(contentResolver, updateUri, this) > 0 - - @Suppress("UNCHECKED_CAST") - override fun load(whereOperatorGroup: OperatorGroup, - orderBy: String?, - wrapper: DatabaseWrapper, - vararg columns: String?): T? { - val cursor = ContentUtils.query(contentResolver, - queryUri, whereOperatorGroup, orderBy, *columns) - if (cursor != null) { - if (cursor.moveToFirst()) { - val model: T = modelAdapter.loadFromCursor(cursor, wrapper) as T - cursor.close() - return model - } - } - return null - } - - @Suppress("UNCHECKED_CAST") - override fun load(wrapper: DatabaseWrapper): T? = load(modelAdapter.getPrimaryConditionClause(this), "", wrapper) as T? -} diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/ContentProviderDatabase.kt b/contentprovider/src/main/kotlin/com/dbflow5/provider/ContentProviderDatabase.kt deleted file mode 100644 index 5b5a3a391be28c9e41c1eb7d3836ecb606905f94..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/ContentProviderDatabase.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentValues -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.database.AndroidDatabaseWrapper - -/** - * Description: Base class providing [ContentValues] for a [DBFlowDatabase]. - */ -abstract class ContentProviderDatabase : AndroidDatabaseWrapper, DBFlowDatabase() { - - private val database - get() = (writableDatabase as? AndroidDatabaseWrapper) - ?: throw IllegalStateException("Invalid DB type used. It must be a type of ${AndroidDatabaseWrapper::class.java}. ") - - override fun updateWithOnConflict(tableName: String, - contentValues: ContentValues, - where: String?, - whereArgs: Array?, - conflictAlgorithm: Int): Long = - database.updateWithOnConflict(tableName, contentValues, where, whereArgs, conflictAlgorithm) - - override fun insertWithOnConflict(tableName: String, - nullColumnHack: String?, - values: ContentValues, - sqLiteDatabaseAlgorithmInt: Int): Long = - database.insertWithOnConflict(tableName, nullColumnHack, values, sqLiteDatabaseAlgorithmInt) -} \ No newline at end of file diff --git a/contentprovider/src/main/kotlin/com/dbflow5/provider/StubContentProvider.kt b/contentprovider/src/main/kotlin/com/dbflow5/provider/StubContentProvider.kt deleted file mode 100644 index 2781a3d04c6a4434519cba2e0ee12aea98f0a771..0000000000000000000000000000000000000000 --- a/contentprovider/src/main/kotlin/com/dbflow5/provider/StubContentProvider.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.dbflow5.provider - -/** - * Description: - */ - -import android.content.ContentProvider -import android.content.ContentValues -import android.database.Cursor -import android.net.Uri - -/** - * Description: Used as a stub, include this in order to work around Android O changes to [ContentProvider] - */ -class StubContentProvider : ContentProvider() { - - override fun insert(uri: Uri, values: ContentValues?): Uri? { - return null - } - - override fun query(uri: Uri, - projection: Array?, - selection: String?, - selectionArgs: Array?, - sortOrder: String?): Cursor? { - return null - } - - override fun onCreate(): Boolean { - return true - } - - override fun update(uri: Uri, values: ContentValues?, - selection: String?, selectionArgs: Array?): Int { - return 0 - } - - override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { - return 0 - } - - override fun getType(uri: Uri): String? { - return null - } -} diff --git a/contentprovider/src/main/resources/base/element/string.json b/contentprovider/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..84f1ffa11d2637b1a18d1771291222fa80af84ec --- /dev/null +++ b/contentprovider/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "contentprovider_library", + "value": "contentprovider_library" + } + ] +} diff --git a/contentprovider/src/test/java/com/dbflow5/provider/ExampleTest.java b/contentprovider/src/test/java/com/dbflow5/provider/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3cce191c2da7df0128db668b19c45a7f0240cc25 --- /dev/null +++ b/contentprovider/src/test/java/com/dbflow5/provider/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.provider; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..432a46af51b63270aeda96357aa172fcf8b844af --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,8 @@ +apply plugin: 'java-library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" diff --git a/core/build.gradle.kts b/core/build.gradle.kts deleted file mode 100644 index e028cf69699d90db2c9367c59ec90f9da49631aa..0000000000000000000000000000000000000000 --- a/core/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") -} - -// project.ext.artifactId = bt_name - -tasks.withType { - kotlinOptions.jvmTarget = "1.8" -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/core/gradle.properties b/core/gradle.properties deleted file mode 100644 index 8bc5e7b08250635b516565cce475f23a37aabd4a..0000000000000000000000000000000000000000 --- a/core/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-core -bt_packaging=jar -bt_artifact_id=dbflow-core \ No newline at end of file diff --git a/core/src/main/java/com/dbflow5/StringUtils.java b/core/src/main/java/com/dbflow5/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2038bb602b3160c759c254494d9fbdae04b8253c --- /dev/null +++ b/core/src/main/java/com/dbflow5/StringUtils.java @@ -0,0 +1,186 @@ +package com.dbflow5; + +import com.dbflow5.sql.SQLiteType; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class StringUtils{ + /** + * @param str str + * @return true if the string is not null, empty string "", or the length is greater than 0 + */ + public static boolean isNullOrEmpty(String str){ + return str == null || str.equals("") || str.isEmpty(); + } + + /** + * @param str str + * @return true if the string is null, empty string "", or the length is less than equal to 0 + */ + public static boolean isNotNullOrEmpty(String str) { + return str != null && !str.equals("") && !str.isEmpty(); + } + + public static StringBuilder appendQuotedIfNeeded(StringBuilder src, String string){ + if ("*".equals(string)) + return src.append(string); + src.append(quoteIfNeeded(string)); + return src; + } + + + private static final char QUOTE = '`'; + private static final Pattern QUOTE_PATTERN = Pattern.compile(QUOTE + ".*" + QUOTE); + + /** + * Helper method to check if name is quoted. + * + * @param string string + * @return true if the name is quoted. We may not want to quote something if its already so. + */ + public static boolean isQuoted(String string){ + return QUOTE_PATTERN.matcher(string).find(); + } + + /** + * @param string The column name to use. + * @return A name in quotes. E.G. index => `index` so we can use keywords as column names without fear + * of clashing. + */ + public static String quote(String string){ + return QUOTE + string.replace(".", "`.`") + QUOTE; + } + + public static String quoteIfNeeded(String string){ +// if (string != null && !isQuoted(string)) { +// return quote(string); +// } else { + return string; +// } + } + + public static String capitalize(String name) { + if (name != null && name.length() != 0) { + char[] chars = name.toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + return new String(chars); + } else { + return name; + } + } + + /** + * Appends the [SQLiteType] to [StringBuilder] + * + * @param stringBuilder src StringBuilder + * @param sqLiteType sqLiteType + * @return result + */ + public static StringBuilder appendSQLiteType(StringBuilder stringBuilder, SQLiteType sqLiteType){ + return stringBuilder.append(sqLiteType.name()); + } + + /** + * Strips quotes out of a identifier if need to do so. + * + * @param string The name ot strip the quotes from. + * @return A non-quoted name. + */ + public static String stripQuotes(String string){ + String ret = string; + if (ret != null && isQuoted(ret)) { + ret = ret.replace("`", ""); + } + return ret; + } + + + /** + * Appends a value only if it's not empty or null + * + * @param stringBuilder src StringBuilder + * @param name The name of the qualifier + * @param value The value to append after the name + * @return This instance + */ + public static StringBuilder appendQualifier(StringBuilder stringBuilder, String name, String value){ + if (value != null && !value.isEmpty()) { + if (name != null) { + stringBuilder.append(name); + } + stringBuilder.append(" "+value+" "); + } + return stringBuilder; + } + + /** + * Appends the value only if its not null + * + * @param stringBuilder src StringBuilder + * @param value If not null, its string representation. + * @return This instance + */ + public static StringBuilder appendOptional(StringBuilder stringBuilder, Object value){ + if (value != null) { + stringBuilder.append(value); + } + return stringBuilder; + } + + /** + * Appends an array of these objects by joining them with a comma with + * [.join] + * + * @param stringBuilder src StringBuilder + * @param objects The array of objects to pass in + * @return This instance + */ + public static StringBuilder appendArray(StringBuilder stringBuilder, Object... objects){ + for(Object obj : objects){ + stringBuilder.append(obj.toString()); + } + return stringBuilder; + } + + /** + * Appends a list of objects by joining them with a comma with + * [.join] + * + * @param stringBuilder src StringBuilder + * @param objects The list of objects to pass in + * @return This instance + */ + public static StringBuilder appendList(StringBuilder stringBuilder, List objects){ + for(Object obj : objects){ + stringBuilder.append(obj.toString()); + } + return stringBuilder; + } + + public static String joinToString(Collection list, String separator){ + StringBuilder builder = new StringBuilder(); + for(T t : list){ + if(builder.toString().length() > 0){ + builder.append(separator); + } + builder.append(t.toString()); + } + return builder.toString(); + } + + public static String joinToString(Collection list, String separator, Function fn){ + StringBuilder builder = new StringBuilder(); + for(T t : list){ + if(builder.toString().length() > 0){ + builder.append(separator); + } + builder.append(fn.apply(t)); + } + return builder.toString(); + } +} + + diff --git a/core/src/main/java/com/dbflow5/annotation/CallSuper.java b/core/src/main/java/com/dbflow5/annotation/CallSuper.java new file mode 100644 index 0000000000000000000000000000000000000000..e6232d4cf561b727f3b8496cde2d726e951b5f6e --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/CallSuper.java @@ -0,0 +1,14 @@ +package com.dbflow5.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +@Documented +@Retention(CLASS) +@Target({METHOD}) +public @interface CallSuper { +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Collate.kt b/core/src/main/java/com/dbflow5/annotation/Collate.java similarity index 92% rename from core/src/main/kotlin/com/dbflow5/annotation/Collate.kt rename to core/src/main/java/com/dbflow5/annotation/Collate.java index caed7b3092802e206ad3c309e918c7d730dc4583..b53ee8b96768b25c068f6df3a3ed4f87869922af 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/Collate.kt +++ b/core/src/main/java/com/dbflow5/annotation/Collate.java @@ -1,9 +1,9 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; /** * Represents a SQL Collate method for comparing string columns. */ -enum class Collate { +public enum Collate { /** * Tells the table creation and condition that we wont use collation diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Column.kt b/core/src/main/java/com/dbflow5/annotation/Column.java similarity index 70% rename from core/src/main/kotlin/com/dbflow5/annotation/Column.kt rename to core/src/main/java/com/dbflow5/annotation/Column.java index 0309cd53a27f03f1b571572b78b8d24dab0eaa9f..fe298d6c53bff4412734e0459160db8d9804fe05 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/Column.kt +++ b/core/src/main/java/com/dbflow5/annotation/Column.java @@ -1,47 +1,52 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; -import com.dbflow5.converter.TypeConverter -import kotlin.reflect.KClass +import com.dbflow5.converter.TypeConverters; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Description: Marks a field as corresponding to a column in the DB. * When adding new columns or changing names, you need to define a new [Migration]. */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FIELD) -annotation class Column( +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface Column{ /** * @return The name of the column. The default is the field name. */ - val name: String = "", + String name() default ""; /** * @return An optional column length */ - val length: Int = -1, + int length = -1; /** * @return Marks the field as having a specified collation to use in it's creation. */ - val collate: Collate = Collate.NONE, + Collate collate() default Collate.NONE; /** * @return Adds a default value for this column when saving. This is a string representation * of the value. * Note this will place it in when saving * to the DB because we cannot know the intention of missing data from a query. */ - val defaultValue: String = "", + String defaultValue() default ""; /** * @return If private, by default this is get{Name}() for "name". To define a custom one, this method specifies the name * of the method only, and not any specific params. So for "getAnotherName()" you would use "AnotherName" as the param. */ - val getterName: String = "", + String getterName() default ""; /** * @return If private, by default this is set{Name}() for "name". To define a custom one, this method specifies the name * of the method only, and not any specific params. So for "setAnotherName(String name)" you would use "AnotherName" as the param. * The params must align exactly to an expected setter, otherwise a compile error ensues. */ - val setterName: String = "", + String setterName() default ""; /** * @return A custom type converter that's only used for this field. It will be created and used in * the Adapter associated with this table. */ - val typeConverter: KClass> = TypeConverter::class) + Class typeConverter() default TypeConverters.TypeConverter.class; +} diff --git a/core/src/main/java/com/dbflow5/annotation/ColumnIgnore.java b/core/src/main/java/com/dbflow5/annotation/ColumnIgnore.java new file mode 100644 index 0000000000000000000000000000000000000000..06ffc767ccd64065613bb4e7f764aab6954b5e6c --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ColumnIgnore.java @@ -0,0 +1,16 @@ +package com.dbflow5.annotation; + +/** + * Description: An annotation used to ignore a column in the [Table.allFields] instance. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.FIELD) +public @interface ColumnIgnore{ + +} diff --git a/core/src/main/java/com/dbflow5/annotation/ColumnMap.java b/core/src/main/java/com/dbflow5/annotation/ColumnMap.java new file mode 100644 index 0000000000000000000000000000000000000000..64ae848f836fa2fe95c88e473d2a32d3997118a2 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ColumnMap.java @@ -0,0 +1,22 @@ +package com.dbflow5.annotation; + +/** + * Description: Maps an arbitrary object and its corresponding fields into a set of columns. It is similar + * to [ForeignKey] except it's not represented in the DB hierarchy. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.FIELD) +public @interface ColumnMap { + /** + * Defines explicit references for a composite [ColumnMap] definition. + * + * @return override explicit usage of all fields and provide custom references. + */ + ColumnMapReference[] references() default {}; +} diff --git a/core/src/main/java/com/dbflow5/annotation/ColumnMapReference.java b/core/src/main/java/com/dbflow5/annotation/ColumnMapReference.java new file mode 100644 index 0000000000000000000000000000000000000000..d3381fafdb802a5ae490f6ee61aa6e2924e5a12b --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ColumnMapReference.java @@ -0,0 +1,42 @@ +package com.dbflow5.annotation; + +import com.dbflow5.converter.TypeConverters; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description: Allows a [ColumnMap] to specify a reference override for its fields. Anything not + * defined here will not be used. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.FIELD) +public @interface ColumnMapReference { + /** + * @return The local column name that will be referenced in the DB + */ + String columnName(); + /** + * @return The column name in the referenced table + */ + String columnMapFieldName(); + /** + * @return The default value for the reference column. Same as [Column.defaultValue] + */ + String defaultValue() default ""; + + /** + * @return A custom type converter that's only used for this field. It will be created and used in + * the Adapter associated with this table. + */ + Class typeConverter() default TypeConverters.TypeConverter.class; + + /** + * @return Specify the [NotNull] annotation here and it will get pasted into the reference definition. + */ + NotNull notNull() default @NotNull( + onNullConflict = ConflictAction.NONE + ); +} diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ConflictAction.kt b/core/src/main/java/com/dbflow5/annotation/ConflictAction.java similarity index 86% rename from core/src/main/kotlin/com/dbflow5/annotation/ConflictAction.kt rename to core/src/main/java/com/dbflow5/annotation/ConflictAction.java index b6a75a3b43bc21ff63125bcf7fd449993dd34f3e..35ecfda2f0fa49ca02629670bbaf9dd94669e5bd 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/ConflictAction.kt +++ b/core/src/main/java/com/dbflow5/annotation/ConflictAction.java @@ -1,10 +1,10 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; /** * This is how to resolve null or unique conflicts with a field marked as [NotNull] * or [Unique] */ -enum class ConflictAction { +public enum ConflictAction { /** * Do nothing and do not specify any algorithm. @@ -61,17 +61,20 @@ enum class ConflictAction { REPLACE; - companion object { - - @JvmStatic - fun getSQLiteDatabaseAlgorithmInt(conflictAction: ConflictAction) = - when (conflictAction) { - ROLLBACK -> 1 - ABORT -> 2 - FAIL -> 3 - IGNORE -> 4 - REPLACE -> 5 - else -> 0 - } + public static int getSQLiteDatabaseAlgorithmInt(ConflictAction conflictAction){ + switch (conflictAction){ + case ROLLBACK: + return 1; + case ABORT: + return 2; + case FAIL: + return 3; + case IGNORE: + return 4; + case REPLACE: + return 5; + default: + return 0; + } } } diff --git a/core/src/main/java/com/dbflow5/annotation/Database.java b/core/src/main/java/com/dbflow5/annotation/Database.java new file mode 100644 index 0000000000000000000000000000000000000000..331f9f361fa1d8f7ffcdb6ee01a7432a4a154189 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Database.java @@ -0,0 +1,53 @@ +package com.dbflow5.annotation; + +/** + * Description: Creates a new database to use in the application. + * + * + * If we specify one DB, then all models do not need to specify a DB. As soon as we specify two, then each + * model needs to define what DB it points to. + * + * + * + * Models will specify which DB it belongs to, + * but they currently can only belong to one DB. + * + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +@Retention(RetentionPolicy.SOURCE) +public @interface Database { + /** + * @return The current version of the DB. Increment it to trigger a DB update. + */ + int version(); + /** + * @return If true, SQLite will throw exceptions when [ForeignKey] constraints are not respected. + * Default is false and will not throw exceptions. + */ + boolean foreignKeyConstraintsEnforced() default false; + /** + * @return Checks for consistency in the DB, if true it will recopy over the prepackage database. + */ + boolean consistencyCheckEnabled() default false; + /** + * @return Keeps a backup for whenever the database integrity fails a "PRAGMA quick_check(1)" that will + * replace the corrupted DB + */ + boolean backupEnabled() default false; + /** + * @return Global default insert conflict that can be applied to any table when it leaves + * its [ConflictAction] as NONE. + */ + ConflictAction insertConflict() default ConflictAction.NONE; + /** + * @return Global update conflict that can be applied to any table when it leaves its + * [ConflictAction] as NONE + */ + ConflictAction updateConflict() default ConflictAction.NONE; +} diff --git a/core/src/main/java/com/dbflow5/annotation/ForeignKey.java b/core/src/main/java/com/dbflow5/annotation/ForeignKey.java new file mode 100644 index 0000000000000000000000000000000000000000..fbab2f26a96df4ddc88b844018943b6aea3bc7bd --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ForeignKey.java @@ -0,0 +1,69 @@ +package com.dbflow5.annotation; + +/** + * Description: + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface ForeignKey { + /** + * Defines explicit references for a composite [ForeignKey] definition. This is no longer required + * as the library will auto-generate references for you based on the other table's primary keys. + * + * @return the set of explicit references if you wish to have different values than default generated. + */ + ForeignKeyReference[] references() default {}; + /** + * @return Default false. When this column is a [ForeignKey] and table object, + * returning true will save the model before adding the fields to save as a foreign key. + * If false, we expect the field to not change and must save the model manually outside + * of the ModelAdapter before saving the child class. + */ + boolean saveForeignKeyModel() default false; + /** + * @return Default false. When this column is a [ForeignKey] and table object, + * returning true will delte the model before deleting its enclosing child class. + * If false, we expect the field to not change and must delete the model manually outside + * of the ModelAdapter before saving the child class. + */ + boolean deleteForeignKeyModel() default false; + /** + * @return Replaces legacy ForeignKeyContainer, this method instructs the code generator to only + * populate the model with the [ForeignKeyReference] defined in this field. This skips + * the Select retrieval convenience. + */ + boolean stubbedRelationship() default false; + /** + * @return If true, during a transaction, FK constraints are not violated immediately until the resulting transaction commits. + * This is useful for out of order foreign key operations. + * @see [Deferred Foreign Key Constraints](http://www.sqlite.org/foreignkeys.html.fk_deferred) + */ + boolean deferred() default false; + /** + * @return an optional table class that this reference points to. It's only used if the field + * is NOT a Model class. + */ + Class tableClass() default Object.class; + /** + * Defines [ForeignKeyAction] action to be performed + * on delete of referenced record. Defaults to [ForeignKeyAction.NO_ACTION]. Used only when + * columnType is [ForeignKey]. + * + * @return [ForeignKeyAction] + */ + ForeignKeyAction onDelete() default ForeignKeyAction.NO_ACTION; + /** + * Defines [ForeignKeyAction] action to be performed + * on update of referenced record. Defaults to [ForeignKeyAction.NO_ACTION]. Used only when + * columnType is [ForeignKey]. + * + * @return [ForeignKeyAction] + */ + ForeignKeyAction onUpdate() default ForeignKeyAction.NO_ACTION; +} diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyAction.kt b/core/src/main/java/com/dbflow5/annotation/ForeignKeyAction.java similarity index 95% rename from core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyAction.kt rename to core/src/main/java/com/dbflow5/annotation/ForeignKeyAction.java index 89d227916b0d4d7db85c2e900aac6e1f2a391cc8..abbe0fa7cda9ac12858b82881887fc40995ab16a 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyAction.kt +++ b/core/src/main/java/com/dbflow5/annotation/ForeignKeyAction.java @@ -1,9 +1,9 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; /** * Actions associated with on update and on delete */ -enum class ForeignKeyAction { +public enum ForeignKeyAction { /** * When a parent key is modified or deleted from the database, no special action is taken */ diff --git a/core/src/main/java/com/dbflow5/annotation/ForeignKeyReference.java b/core/src/main/java/com/dbflow5/annotation/ForeignKeyReference.java new file mode 100644 index 0000000000000000000000000000000000000000..05f713508e8820dd64208b16a800df2b4b469fc4 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ForeignKeyReference.java @@ -0,0 +1,37 @@ +package com.dbflow5.annotation; + +/** + * Description: Used inside of [ForeignKey.references], describes the + * local column name, type, and referencing table column name. + *

+ *

+ * Note: the type of the local column must match the + * column type of the referenced column. By using a field as a Model object, + * you will need to ensure the same types are used. + */ + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +public @interface ForeignKeyReference { + /** + * @return The local column name that will be referenced in the DB + */ + String columnName(); + + /** + * @return The column name in the referenced table + */ + String foreignKeyColumnName(); + + /** + * @return The default value for the reference column. Same as [Column.defaultValue] + */ + String defaultValue() default ""; + + /** + * @return Specify the [NotNull] annotation here and it will get pasted into the reference definition. + */ + NotNull notNull() default @NotNull(onNullConflict = ConflictAction.NONE); +} diff --git a/core/src/main/java/com/dbflow5/annotation/Fts3.java b/core/src/main/java/com/dbflow5/annotation/Fts3.java new file mode 100644 index 0000000000000000000000000000000000000000..961ff7b63079c4b3a9fce77554b888e82189ae7c --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Fts3.java @@ -0,0 +1,20 @@ +package com.dbflow5.annotation; + +/** + * Description: Creates a class using the SQLITE FTS3 [https://www.sqlite.org/fts3.html] + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Fts3{ + +} + + + + diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Fts.kt b/core/src/main/java/com/dbflow5/annotation/Fts4.java similarity index 44% rename from core/src/main/kotlin/com/dbflow5/annotation/Fts.kt rename to core/src/main/java/com/dbflow5/annotation/Fts4.java index 074330d407d91b4a368c22e57ac4cf61c99929aa..c2debfb90437eda40d5a818de26a7fdf4185c30d 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/Fts.kt +++ b/core/src/main/java/com/dbflow5/annotation/Fts4.java @@ -1,26 +1,20 @@ -package com.dbflow5.annotation - -import kotlin.reflect.KClass - -/** - * Description: Creates a class using the SQLITE FTS3 [https://www.sqlite.org/fts3.html] - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS) -annotation class Fts3 +package com.dbflow5.annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Description: Creates a class using the SQLITE FTS4 [https://www.sqlite.org/fts3.html] */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS) -annotation class Fts4( +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Fts4 { /** * Optionally points to a content table that fills this FTS4 with content. * The content option allows FTS4 to forego storing the text being indexed and * results in significant space savings. */ - val contentTable: KClass<*> = Any::class -) - + Class contentTable() default Object.class; +} \ No newline at end of file diff --git a/core/src/main/java/com/dbflow5/annotation/Index.java b/core/src/main/java/com/dbflow5/annotation/Index.java new file mode 100644 index 0000000000000000000000000000000000000000..51f7a0c70aa7a03e54ed432adf1b369ca4964835 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Index.java @@ -0,0 +1,20 @@ +package com.dbflow5.annotation; + +/** + * Description: Creates an index for a specified [Column]. A single column can belong to multiple + * indexes within the same table if you wish. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface Index { + /** + * @return The set of index groups that this index belongs to. + */ + int[] indexGroups() default {}; +} diff --git a/core/src/main/java/com/dbflow5/annotation/IndexGroup.java b/core/src/main/java/com/dbflow5/annotation/IndexGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..88bf8152a286b4de2141c2d9dec5e7499f758e29 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/IndexGroup.java @@ -0,0 +1,31 @@ +package com.dbflow5.annotation; + +/** + * Description: + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface IndexGroup { + int INDEX_GENERIC = -1; + + /** + * @return The number that each contained [Index] points to, so they can be combined into a single index. + * If [.INDEX_GENERIC], this will assume a generic index that covers the whole table. + */ + int number() default INDEX_GENERIC; + /** + * @return The name of this index. It must be unique from other [IndexGroup]. + */ + String name(); + /** + * @return If true, this will disallow duplicate values to be inserted into the table. + */ + boolean unique() default false; +} + diff --git a/core/src/main/java/com/dbflow5/annotation/InheritedColumn.java b/core/src/main/java/com/dbflow5/annotation/InheritedColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..1c8d51f3de74863497fe842f251a00d2c7635edf --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/InheritedColumn.java @@ -0,0 +1,27 @@ +package com.dbflow5.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description: Allows [Table] to inherit fields from other objects to make it part of the DB table. + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface InheritedColumn { + /** + * @return The column annotation as if it was part of the class + */ + Column column(); + /** + * @return The field name that an inherited column uses. It must match exactly case-by-case to the field you're referencing. + * If the field is private, the [Column] allows you to define getter and setters for it. + */ + String fieldName(); + /** + * @return If specified other than [ConflictAction.NONE], then we assume [NotNull]. + */ + ConflictAction nonNullConflict() default ConflictAction.NONE; +} diff --git a/core/src/main/java/com/dbflow5/annotation/InheritedPrimaryKey.java b/core/src/main/java/com/dbflow5/annotation/InheritedPrimaryKey.java new file mode 100644 index 0000000000000000000000000000000000000000..ed090783ac9e91fec81a6a74e72e6cb077bd10ec --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/InheritedPrimaryKey.java @@ -0,0 +1,28 @@ +package com.dbflow5.annotation; + +/** + * Description: Allows you to specify a non-Column to be inherited and used as a [PrimaryKey] + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface InheritedPrimaryKey { + /** + * @return The primary key annotation as if it was part of the class + */ + PrimaryKey primaryKey(); + /** + * @return The column annotation as if it was part of the class + */ + Column column(); + /** + * @return The field name that an inherited column uses. It must match exactly case-by-case to the field you're referencing. + * If the field is private, the [PrimaryKey] allows you to define getter and setters for it. + */ + String fieldName(); +} diff --git a/core/src/main/java/com/dbflow5/annotation/IntDef.java b/core/src/main/java/com/dbflow5/annotation/IntDef.java new file mode 100644 index 0000000000000000000000000000000000000000..01d51514ea0db8d7c08d8307e134b62b52269c54 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/IntDef.java @@ -0,0 +1,26 @@ +package com.dbflow5.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@Retention(SOURCE) +@Target({ANNOTATION_TYPE}) +public @interface IntDef { + /** Defines the allowed constants for this element */ + int[] value() default {}; + + /** Defines whether the constants can be used as a flag, or just as an enum (the default) */ + boolean flag() default false; + + /** + * Whether any other values are allowed. Normally this is + * not the case, but this allows you to specify a set of + * expected constants, which helps code completion in the IDE + * and documentation generation and so on, but without + * flagging compilation warnings if other values are specified. + */ + boolean open() default false; +} diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ManyToMany.kt b/core/src/main/java/com/dbflow5/annotation/ManyToMany.java similarity index 68% rename from core/src/main/kotlin/com/dbflow5/annotation/ManyToMany.kt rename to core/src/main/java/com/dbflow5/annotation/ManyToMany.java index 21d07fd89d2494f499b01177f2464b2fbead1ee8..6fc52984e01f6918b0c443df71325dfd339c8f8d 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/ManyToMany.kt +++ b/core/src/main/java/com/dbflow5/annotation/ManyToMany.java @@ -1,42 +1,47 @@ -package com.dbflow5.annotation - -import kotlin.reflect.KClass +package com.dbflow5.annotation; /** * Description: Builds a many-to-many relationship with another [Table]. Only one table needs to specify * the annotation and its assumed that they use primary keys only. The generated * class will contain an auto-incrementing primary key by default. */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class ManyToMany( + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +public @interface ManyToMany{ /** * @return The other table class by which this will get merged. */ - val referencedTable: KClass<*>, + Class referencedTable(); /** * @return A name that we use as the column name for the referenced table in the * generated ManyToMany table class. */ - val referencedTableColumnName: String = "", + String referencedTableColumnName() default ""; /** * @return A name that we use as the column name for this specific table's name. */ - val thisTableColumnName: String = "", + String thisTableColumnName() default ""; /** * @return By default, we generate an auto-incrementing [Long] [PrimaryKey]. * If false, all [PrimaryKey] of the corresponding tables will be placed as [ForeignKey] and [PrimaryKey] * of the generated table instead of using an autoincrementing Long [PrimaryKey]. */ - val generateAutoIncrement: Boolean = true, + boolean generateAutoIncrement() default true; /** * @return by default, we append {selfTable}{generatedClassSeparator}{referencedTable} or "User_Follower", * for example. If you want different name, change this. */ - val generatedTableClassName: String = "", + String generatedTableClassName() default ""; /** * @return by default the Models referenced here are not saved prior to saving this * object for obvious efficiency reasons. * @see ForeignKey.saveForeignKeyModel */ - val saveForeignKeyModels: Boolean = false) + boolean saveForeignKeyModels() default false; +} diff --git a/core/src/main/java/com/dbflow5/annotation/Migration.java b/core/src/main/java/com/dbflow5/annotation/Migration.java new file mode 100644 index 0000000000000000000000000000000000000000..cd60bf6addd6ad0572fb73a30bfb57cbb3c82371 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Migration.java @@ -0,0 +1,32 @@ +package com.dbflow5.annotation; + +/** + * Description: Marks a Migration class to be included in DB construction. The class using this annotation + * must implement the Migration interface. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +public @interface Migration { + /** + * @return The version the migration will trigger at. + */ + int version(); + /** + * @return Specify the database class that this migration belongs to. + */ + Class database(); + /** + * @return If number greater than -1, the migrations are in run in reverse priority, + * meaning ones from the same [version] get ordered from + * lowest to highest number. if they are the same priority, + * there is no telling which one is executed first. The + * annotation com.dbflow5.processor will process in order it finds the classes. + */ + int priority() default -1; +} diff --git a/core/src/main/java/com/dbflow5/annotation/ModelCacheField.java b/core/src/main/java/com/dbflow5/annotation/ModelCacheField.java new file mode 100644 index 0000000000000000000000000000000000000000..adb2621e268ecdb4ef31b3af8edd05cecfce4524 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ModelCacheField.java @@ -0,0 +1,16 @@ +package com.dbflow5.annotation; + +/** + * Description: marks a single field as a ModelCache creator that is used in the corresponding ModelAdapter. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface ModelCacheField{ + +} diff --git a/core/src/main/java/com/dbflow5/annotation/ModelView.java b/core/src/main/java/com/dbflow5/annotation/ModelView.java new file mode 100644 index 0000000000000000000000000000000000000000..44daab4088e55160be42ba7424ab903dffa82b20 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ModelView.java @@ -0,0 +1,54 @@ +package com.dbflow5.annotation; + +import com.dbflow5.sql.Query; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Author: andrewgrosner + * Description: Marks a class as being an SQL VIEW definition. It must extend BaseModelView and have + * a single public, static, final field that is annotated with [ModelViewQuery] and be a [Query]. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +public @interface ModelView { + /** + * @return The name of this view. Default is the class name. + */ + String name() default ""; + /** + * @return The class of the database this corresponds to. + */ + Class database(); + /** + * @return When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . + * The only required annotated field becomes The [PrimaryKey] + * or [PrimaryKey.autoincrement]. + */ + boolean allFields() default true; + + /** + * @return If true, we throw away checks for column indexing and simply assume that the cursor returns + * all our columns in order. This may provide a slight performance boost. + */ + boolean orderedCursorLookUp() default false; + /** + * @return When true, we reassign the corresponding Model's fields to default values when loading + * from cursor. If false, we assign values only if present in Cursor. + */ + boolean assignDefaultValuesFromCursor() default true; + /** + * @return The higher the number, the order by which the creation of this class gets called. + * Useful for creating ones that depend on another [ModelView]. + */ + int priority() default 0; + + /** + * @return When false, this view gets generated and associated with database, however it will not immediately + * get created upon startup. This is useful for keeping around legacy tables for migrations. + */ + boolean createWithDatabase() default true; +} diff --git a/core/src/main/java/com/dbflow5/annotation/ModelViewQuery.java b/core/src/main/java/com/dbflow5/annotation/ModelViewQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..ac3b0a6b89abc089f275a8bd6458b1587aae574c --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/ModelViewQuery.java @@ -0,0 +1,19 @@ +package com.dbflow5.annotation; + +import com.dbflow5.sql.Query; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description: Represents a field that is a [Query]. This is only meant to be used as a query + * reference in [ModelView]. This is so the annotation com.dbflow5.processor knows how to access the query of + * the view. + */ +@Target(value = {ElementType.METHOD/*, ElementType.PROPERTY_GETTER*/}) +@Retention(RetentionPolicy.SOURCE) +public @interface ModelViewQuery{ + +} diff --git a/core/src/main/kotlin/com/dbflow5/annotation/MultiCacheField.kt b/core/src/main/java/com/dbflow5/annotation/MultiCacheField.java similarity index 32% rename from core/src/main/kotlin/com/dbflow5/annotation/MultiCacheField.kt rename to core/src/main/java/com/dbflow5/annotation/MultiCacheField.java index f025b2c566febbbfa47fe9979125b332f476e8ea..a2300d695c6c6f5d26f9cdab7de432e37bad7b68 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/MultiCacheField.kt +++ b/core/src/main/java/com/dbflow5/annotation/MultiCacheField.java @@ -1,9 +1,17 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; /** * Description: Marks a field as the IMultiKeyCacheModel that we use to convert multiple fields into * a single key for caching. */ -@Target(AnnotationTarget.FIELD) -@Retention(AnnotationRetention.SOURCE) -annotation class MultiCacheField + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface MultiCacheField{ + +} diff --git a/core/src/main/java/com/dbflow5/annotation/MultipleManyToMany.java b/core/src/main/java/com/dbflow5/annotation/MultipleManyToMany.java new file mode 100644 index 0000000000000000000000000000000000000000..7c03bedef4f53c25139d1050dc0b0b495fb29946 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/MultipleManyToMany.java @@ -0,0 +1,16 @@ +package com.dbflow5.annotation; + +/** + * Description: Provides ability to add multiple [ManyToMany] annotations at once. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(value = {ElementType.TYPE, ElementType.TYPE_USE}) +public @interface MultipleManyToMany{ + ManyToMany[] value(); +} diff --git a/core/src/main/kotlin/com/dbflow5/annotation/NotNull.kt b/core/src/main/java/com/dbflow5/annotation/NotNull.java similarity index 62% rename from core/src/main/kotlin/com/dbflow5/annotation/NotNull.kt rename to core/src/main/java/com/dbflow5/annotation/NotNull.java index 2461d2c57e58396dcf7ea1276ca61aa7300d1044..eb3ced01159292697cc254f4f7622f36915dc903 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/NotNull.kt +++ b/core/src/main/java/com/dbflow5/annotation/NotNull.java @@ -1,4 +1,4 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; /** * Description: Specifies that a [Column] is not null. @@ -9,12 +9,19 @@ package com.dbflow5.annotation * [ConflictAction] specified. If you need one to support null, then you need to be explicit * in the [ForeignKeyReference]. */ -@Target(AnnotationTarget.FIELD) -@Retention(AnnotationRetention.SOURCE) -annotation class NotNull( + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface NotNull { /** * Defines how to handle conflicts for not null column * * @return a [com.dbflow5.annotation.ConflictAction] enum */ - val onNullConflict: ConflictAction = ConflictAction.FAIL) + ConflictAction onNullConflict() default ConflictAction.FAIL; +} diff --git a/core/src/main/java/com/dbflow5/annotation/OneToMany.java b/core/src/main/java/com/dbflow5/annotation/OneToMany.java new file mode 100644 index 0000000000000000000000000000000000000000..727f7767f4103a528eaf9a58a9eb8571928809c5 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/OneToMany.java @@ -0,0 +1,35 @@ +package com.dbflow5.annotation; + +/** + * Description: Describes a 1-many relationship. It applies to some method that returns a [List] of Model objects. + * This annotation can handle loading, deleting, and saving when the current data changes. By default it will call the + * associated method when the containing class operates. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value = {ElementType.METHOD, ElementType.FIELD/*, + ElementType.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER*/}) +@Retention(RetentionPolicy.SOURCE) +public @interface OneToMany { + /** + * @return The methods you wish to call it from. By default it's loaded out of the DB. + */ + OneToManyMethod[] oneToManyMethods() default {OneToManyMethod.LOAD}; + /** + * @return The name of the list variable to use. If is left blank, we will remove the "get" and then decapitalize the remaining name. + */ + String variableName() default ""; + /** + * @return If true, the code generated for this relationship done as efficiently as possible. + * It will not work on nested relationships, caching, and other code that requires overriding of BaseModel or Model operations. + */ + boolean efficientMethods() default true; +} + + + diff --git a/core/src/main/java/com/dbflow5/annotation/OneToManyMethod.java b/core/src/main/java/com/dbflow5/annotation/OneToManyMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..9232f2bf128a2afe68914d87e514b1ecfa8ca3bc --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/OneToManyMethod.java @@ -0,0 +1,24 @@ +package com.dbflow5.annotation; + +public enum OneToManyMethod { + /** + * Load this relationship when the parent model loads from the database. This is called before the OnLoadFromCursor + * method, but after other columns load. + */ + LOAD, + + /** + * Inserts code to delete the results returned from the List relationship when the parent model is deleted. + */ + DELETE, + + /** + * Inserts code to save the list of models when the parent model is saved. + */ + SAVE, + + /** + * Shorthand to support all options. + */ + ALL +} diff --git a/core/src/main/java/com/dbflow5/annotation/PrimaryKey.java b/core/src/main/java/com/dbflow5/annotation/PrimaryKey.java new file mode 100644 index 0000000000000000000000000000000000000000..c92fe37d4b0dde56e39848a3ce6c55b0fe1d739f --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/PrimaryKey.java @@ -0,0 +1,29 @@ +package com.dbflow5.annotation; + +/** + * Description: + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface PrimaryKey { + /** + * Specifies if the column is autoincrementing or not + */ + boolean autoincrement() default false; + /** + * Specifies the column to be treated as a ROWID but is not an [.autoincrement]. This + * overrides [.autoincrement] and is mutually exclusive. + */ + boolean rowID() default false; + /** + * @return When true, we simple do {columnName} > 0 when checking for it's existence if [.autoincrement] + * is true. If not, we do a full database SELECT exists. + */ + boolean quickCheckAutoIncrement() default false; +} diff --git a/core/src/main/java/com/dbflow5/annotation/QueryModel.java b/core/src/main/java/com/dbflow5/annotation/QueryModel.java new file mode 100644 index 0000000000000000000000000000000000000000..6dda65afb17bf2dd2193bd757e7cba68fc9f1f9e --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/QueryModel.java @@ -0,0 +1,37 @@ +package com.dbflow5.annotation; + +/** + * Description: Marks a Model class as NOT a [Table], but generates code for retrieving data from a + * generic query + */ +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +public @interface QueryModel { + /** + * @return Specify the class of the database to use. + */ + Class database(); + + /** + * @return If true, all accessible, non-static, and non-final fields are treated as valid fields. + */ + boolean allFields() default true; + + /** + * @return If true, we throw away checks for column indexing and simply assume that the cursor returns + * all our columns in order. This may provide a slight performance boost. + */ + boolean orderedCursorLookUp() default false; + + /** + * @return When true, we reassign the corresponding Model's fields to default values when loading + * from cursor. If false, we assign values only if present in Cursor. + */ + boolean assignDefaultValuesFromCursor() default true; +} + diff --git a/core/src/main/java/com/dbflow5/annotation/Table.java b/core/src/main/java/com/dbflow5/annotation/Table.java new file mode 100644 index 0000000000000000000000000000000000000000..af9d84b03fc0a96757a675923a56b29efcf42b33 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Table.java @@ -0,0 +1,108 @@ +package com.dbflow5.annotation; + +import com.dbflow5.annotation.ConflictAction; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Author: andrewgrosner + * Description: Marks a class as being a table for only ONE DB. It must implement the Model interface and all fields MUST be package private. + * This will generate a $Table and $Adapter class. The $Table class generates static final column name variables to reference in queries. + * The $Adapter class defines how to retrieve and store this object as well as other methods for acting on model objects in the database. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Table { + int DEFAULT_CACHE_SIZE = 25; + /** + * @return Specifies a different name for the table than the name of the Model class. + */ + String name() default ""; + /** + * @return Specify the database class that this table belongs to. It must have the [Database] annotation. + */ + Class database(); + /** + * @return Specify the general conflict algorithm used by this table when updating records. + */ + com.dbflow5.annotation.ConflictAction updateConflict() default com.dbflow5.annotation.ConflictAction.NONE; + /** + * @return Specify the general insert conflict algorithm used by this table. + */ + com.dbflow5.annotation.ConflictAction insertConflict() default com.dbflow5.annotation.ConflictAction.NONE; + /** + * @return An optional [ConflictAction] that we append to creation for conflict handling in PK. + */ + com.dbflow5.annotation.ConflictAction primaryKeyConflict() default com.dbflow5.annotation.ConflictAction.NONE; + /** + * @return When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . + * The only required annotated field becomes The [PrimaryKey] + * or [PrimaryKey.autoincrement]. + */ + boolean allFields() default true; + /** + * @return If true, all private boolean fields will use "is" instead of "get" for its getter and + * "set" without the "is" if it starts with "is" + */ + boolean useBooleanGetterSetters() default true; + /** + * @return If true, caching mechanism is enabled. This works for single primary key tables. For + * multi-primary key tables, IMultiKeyCacheModel interface is required to specify the caching key. + */ + boolean cachingEnabled() default false; + /** + * @return If true, we throw away checks for column indexing and simply assume that the cursor returns + * all our columns in order. This may provide a slight performance boost. + */ + boolean orderedCursorLookUp() default false; + /** + * @return When true, we reassign the corresponding Model's fields to default values when loading + * from cursor. If false, we assign values only if present in Cursor. + */ + boolean assignDefaultValuesFromCursor() default true; + /** + * @return When false, this table gets generated and associated with database, however it will not immediately + * get created upon startup. This is useful for keeping around legacy tables for migrations. + */ + boolean createWithDatabase() default true; + + /** + * If true this table will be created as a TEMP table. Pair this with [createWithDatabase] + * to properly not create the table. + */ + boolean temporary() default false; + + /** + * If true, generates ContentValues bindings for a table. By default it no longer does. + */ + boolean generateContentValues() default false; + /** + * @return The cache size for this Table. + */ + int cacheSize() default 25; + /** + * @return Declares a set of UNIQUE columns with the corresponding [ConflictAction]. A [Column] + * will point to this group using [Unique.uniqueGroups] + */ + UniqueGroup[] uniqueColumnGroups() default {}; + /** + * @return The set of INDEX clauses that specific columns can define to belong to, using the [Index] annotation. + * The generated Index properties belong to the corresponding property class to this table. + */ + com.dbflow5.annotation.IndexGroup[] indexGroups() default {}; + /** + * @return A set of inherited accessible fields not necessarily defined as columns in the super class of this table. + * Each must be accessible via: public, package private, or protected or getter/setters. + */ + com.dbflow5.annotation.InheritedColumn[] inheritedColumns() default {}; + /** + * @return A set of inherited accessible fields not necessarily defined as columns in the super class of this table. + * Each must be accessible via: public, package private, or protected or getter/setters. + */ + InheritedPrimaryKey[] inheritedPrimaryKeys() default {}; + +} + diff --git a/core/src/main/kotlin/com/dbflow5/annotation/TypeConverter.kt b/core/src/main/java/com/dbflow5/annotation/TypeConverter.java similarity index 30% rename from core/src/main/kotlin/com/dbflow5/annotation/TypeConverter.kt rename to core/src/main/java/com/dbflow5/annotation/TypeConverter.java index 6e863a591455350bbf4001f171161d5408782265..e29dc4b9cd39059c23c0c9b58893413c1b11dfc2 100644 --- a/core/src/main/kotlin/com/dbflow5/annotation/TypeConverter.kt +++ b/core/src/main/java/com/dbflow5/annotation/TypeConverter.java @@ -1,17 +1,18 @@ -package com.dbflow5.annotation +package com.dbflow5.annotation; -import kotlin.reflect.KClass +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Author: andrewgrosner * Description: Marks a class as being a TypeConverter. A type converter will turn a non-model, non-SQLiteTyped class into * a valid database type. */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class TypeConverter( - /** - * @return Specify a set of subclasses by which the [TypeConverter] registers for. For - * each one, this will create a new instance of the converter. - */ - val allowedSubtypes: Array> = []) +@Retention(RetentionPolicy.CLASS) +//@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) +@Target(value = {ElementType.TYPE, ElementType.PACKAGE}) +public @interface TypeConverter { + Class[] allowedSubtypes() default {}; +} diff --git a/core/src/main/java/com/dbflow5/annotation/Unique.java b/core/src/main/java/com/dbflow5/annotation/Unique.java new file mode 100644 index 0000000000000000000000000000000000000000..a524803a5389f6dcbd941f661e7d3c0986a026e7 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/Unique.java @@ -0,0 +1,31 @@ +package com.dbflow5.annotation; + +/** + * Description: Marks the field as unique, meaning its value cannot be repeated. It is, however, + * NOT a primary key. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.FIELD) +public @interface Unique { + /** + * @return if field is unique. If false, we expect [.uniqueGroups] to be specified.` + */ + boolean unique() default true; + /** + * @return Marks a unique field as part of a unique group. For every unique number entered, + * it will generate a UNIQUE() column statement. + */ + int[] uniqueGroups() default {}; + /** + * Defines how to handle conflicts for a unique column + * + * @return a [ConflictAction] enum + */ + ConflictAction onUniqueConflict() default ConflictAction.FAIL; +} diff --git a/core/src/main/java/com/dbflow5/annotation/UniqueGroup.java b/core/src/main/java/com/dbflow5/annotation/UniqueGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..fd392cd5a7637b53a0624c2932c8917f8a283f84 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/UniqueGroup.java @@ -0,0 +1,24 @@ +package com.dbflow5.annotation; + +/** + * Description: + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface UniqueGroup { + /** + * The number that columns point to to use this group + */ + int groupNumber() default 0; + /** + * The conflict action that this group takes. + */ + ConflictAction uniqueConflict() default ConflictAction.FAIL; + +} diff --git a/core/src/main/java/com/dbflow5/annotation/WorkerThread.java b/core/src/main/java/com/dbflow5/annotation/WorkerThread.java new file mode 100644 index 0000000000000000000000000000000000000000..1a268e06e53a3c904593a1040977d9148c3930c5 --- /dev/null +++ b/core/src/main/java/com/dbflow5/annotation/WorkerThread.java @@ -0,0 +1,15 @@ +package com.dbflow5.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) +public @interface WorkerThread { +} \ No newline at end of file diff --git a/core/src/main/java/com/dbflow5/converter/TypeConverters.java b/core/src/main/java/com/dbflow5/converter/TypeConverters.java new file mode 100644 index 0000000000000000000000000000000000000000..6667a5fec50e3ca603b5ae3cdad9f1d086371fc1 --- /dev/null +++ b/core/src/main/java/com/dbflow5/converter/TypeConverters.java @@ -0,0 +1,196 @@ +package com.dbflow5.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.*; + +public class TypeConverters { + /** + * Author: andrewgrosner + * Description: This class is responsible for converting the stored database value into the field value in + * a Model. + */ + @com.dbflow5.annotation.TypeConverter + public abstract static class TypeConverter { + + /** + * Converts the ModelClass into a DataClass + * + * @param model this will be called upon syncing + * @return The DataClass value that converts into a SQLite type + */ + public abstract DataClass getDBValue(ModelClass model); + + /** + * Converts a DataClass from the DB into a ModelClass + * + * @param data This will be called when the model is loaded from the DB + * @return The ModelClass value that gets set in a Model that holds the data class. + */ + public abstract ModelClass getModelValue(DataClass data); + } + + /** + * Description: Defines how we store and retrieve a [java.math.BigDecimal] + */ + @com.dbflow5.annotation.TypeConverter + public static class BigDecimalConverter extends TypeConverter { + @Override + public String getDBValue(BigDecimal model) { + return model.toString(); + } + + @Override + public BigDecimal getModelValue(String data) { + if (data != null) { + return new BigDecimal(data); + } + return null; + } + } + + /** + * Description: Defines how we store and retrieve a [java.math.BigInteger] + */ + @com.dbflow5.annotation.TypeConverter + public static class BigIntegerConverter extends TypeConverter { + @Override + public String getDBValue(BigInteger model) { + return model.toString(); + } + + @Override + public BigInteger getModelValue(String data) { + if (data != null) { + return new BigInteger(data); + } + return null; + } + } + + /** + * Description: Converts a boolean object into an Integer for database storage. + */ + @com.dbflow5.annotation.TypeConverter + public static class BooleanConverter extends TypeConverter { + @Override + public Integer getDBValue(Boolean model) { + if (model != null) { + return model ? 1 : 0; + } + return null; + } + + @Override + public Boolean getModelValue(Integer data) { + return null; + } + } + + /** + * Description: Defines how we store and retrieve a [java.util.Calendar] + */ + @com.dbflow5.annotation.TypeConverter(allowedSubtypes = {GregorianCalendar.class}) + public static class CalendarConverter extends TypeConverter { + + @Override + public Long getDBValue(Calendar model) { + return model.getTimeInMillis(); + } + + @Override + public Calendar getModelValue(Long data) { + if (data != null) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(data); + return calendar; + } + return null; + } + } + + /** + * Description: Converts a [Character] into a [String] for database storage. + */ + @com.dbflow5.annotation.TypeConverter + public static class CharConverter extends TypeConverter { + @Override + public String getDBValue(Character model) { + if (model != null) { + char[] charArray = new char[]{model}; + return new String(charArray); + } + return null; + } + + @Override + public Character getModelValue(String data) { + if (data != null) + return data.charAt(0); + else + return null; + } + } + + /** + * Description: Defines how we store and retrieve a [java.util.Date] + */ + @com.dbflow5.annotation.TypeConverter + public static class DateConverter extends TypeConverter { + + @Override + public Long getDBValue(Date model) { + return model.getTime(); + } + + @Override + public Date getModelValue(Long data) { + if (data == null) + return null; + else + return new Date(data); + } + } + + /** + * Description: Defines how we store and retrieve a [java.sql.Date] + */ + @com.dbflow5.annotation.TypeConverter(allowedSubtypes = {Time.class, Timestamp.class}) + public static class SqlDateConverter extends TypeConverter { + @Override + public Long getDBValue(java.sql.Date model) { + return model.getTime(); + } + + @Override + public java.sql.Date getModelValue(Long data) { + if (data == null) + return null; + else + return new java.sql.Date(data); + } + } + + /** + * Description: Responsible for converting a [UUID] to a [String]. + * + * @author Andrew Grosner (fuzz) + */ + @com.dbflow5.annotation.TypeConverter + public static class UUIDConverter extends TypeConverter { + @Override + public String getDBValue(UUID model) { + return model.toString(); + } + + @Override + public UUID getModelValue(String data) { + if (data == null) { + return null; + } else + return UUID.fromString(data); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dbflow5/data/Blob.java b/core/src/main/java/com/dbflow5/data/Blob.java new file mode 100644 index 0000000000000000000000000000000000000000..c859cf5cab8656f1b0d0f5f58cb1042c0b8a9620 --- /dev/null +++ b/core/src/main/java/com/dbflow5/data/Blob.java @@ -0,0 +1,20 @@ +package com.dbflow5.data; + +/** + * Description: Provides a way to support blob format data. + */ +public final class Blob{ + private byte[] blob; + + public Blob(byte[] blob) { + this.blob = blob; + } + + public byte[] getBlob() { + return blob; + } + + public void setBlob(byte[] blob) { + this.blob = blob; + } +} diff --git a/core/src/main/kotlin/com/dbflow5/sql/Query.kt b/core/src/main/java/com/dbflow5/sql/Query.java similarity index 67% rename from core/src/main/kotlin/com/dbflow5/sql/Query.kt rename to core/src/main/java/com/dbflow5/sql/Query.java index 50b29b12fd1ea0307159406c1167963874c20d73..ab0c237a0d644ad146d5d82ab234b730efd53189 100644 --- a/core/src/main/kotlin/com/dbflow5/sql/Query.kt +++ b/core/src/main/java/com/dbflow5/sql/Query.java @@ -1,12 +1,12 @@ -package com.dbflow5.sql +package com.dbflow5.sql; /** * Description: The basic interface for something that has a piece of a query. */ -interface Query { +public interface Query { /** * @return the SQL query statement here */ - val query: String + String getQuery(); } diff --git a/core/src/main/java/com/dbflow5/sql/QueryCloneable.java b/core/src/main/java/com/dbflow5/sql/QueryCloneable.java new file mode 100644 index 0000000000000000000000000000000000000000..cd8c09b6a131d1b9af091475898adef9fcf7790c --- /dev/null +++ b/core/src/main/java/com/dbflow5/sql/QueryCloneable.java @@ -0,0 +1,8 @@ +package com.dbflow5.sql; + +/** + * Description: + */ +public interface QueryCloneable { + T cloneSelf(); +} \ No newline at end of file diff --git a/core/src/main/java/com/dbflow5/sql/SQLiteType.java b/core/src/main/java/com/dbflow5/sql/SQLiteType.java new file mode 100644 index 0000000000000000000000000000000000000000..4ac3e75b6fd966e3a392917564d78a03c87e2d57 --- /dev/null +++ b/core/src/main/java/com/dbflow5/sql/SQLiteType.java @@ -0,0 +1,73 @@ +package com.dbflow5.sql; + +import com.dbflow5.data.Blob; + +import java.util.HashMap; +import java.util.Map; + +/** + * Description: Represents a type that SQLite understands. + */ +public enum SQLiteType { + + /** + * Represents an integer number in the DB. + */ + INTEGER, + + /** + * Represents a floating-point, precise number. + */ + REAL, + + /** + * Represents text. + */ + TEXT, + + /** + * A column defined by [byte[]] data. Usually reserved for images or larger pieces of content. + */ + BLOB; + + public static Map sTypeMap = new HashMap<>(); + + static { + sTypeMap.put(Byte.TYPE.getName(), INTEGER); + sTypeMap.put(Short.TYPE.getName(), INTEGER); + sTypeMap.put(Integer.TYPE.getName(), INTEGER); + sTypeMap.put(Long.TYPE.getName(), INTEGER); + sTypeMap.put(Float.TYPE.getName(), REAL); + sTypeMap.put(Double.TYPE.getName(), REAL); + sTypeMap.put(Boolean.TYPE.getName(), INTEGER); + sTypeMap.put(Character.TYPE.getName(), TEXT); + + sTypeMap.put(Byte[].class.getName(), BLOB); + sTypeMap.put(Byte.class.getName(), INTEGER); + sTypeMap.put(Short.class.getName(), INTEGER); + sTypeMap.put(Integer.class.getName(), INTEGER); + sTypeMap.put(Long.class.getName(), INTEGER); + sTypeMap.put(Float.class.getName(), REAL); + sTypeMap.put(Double.class.getName(), REAL); + sTypeMap.put(Boolean.class.getName(), INTEGER); + sTypeMap.put(Character.class.getName(), TEXT); + sTypeMap.put(CharSequence.class.getName(), TEXT); + sTypeMap.put(String.class.getName(), TEXT); + sTypeMap.put(Byte[].class.getName(), TEXT); + sTypeMap.put(Blob.class.getName(), TEXT); + } + + /** + * Returns the [SQLiteType] for this class + * + * @param className The fully qualified class name + * @return The type from the class name + */ + public static SQLiteType get(String className){ + return sTypeMap.get(className); + } + + public static boolean containsClass(String className){ + return sTypeMap.containsKey(className); + } +} diff --git a/core/src/main/kotlin/com/dbflow5/StringUtils.kt b/core/src/main/kotlin/com/dbflow5/StringUtils.kt deleted file mode 100644 index 923135a607396a1779222f3d4012efbbfa7a9e42..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/StringUtils.kt +++ /dev/null @@ -1,118 +0,0 @@ -@file:JvmName("StringUtils") - -package com.dbflow5 - -import com.dbflow5.sql.SQLiteType -import java.util.regex.Pattern - - -/** - * @return true if the string is not null, empty string "", or the length is greater than 0 - */ -fun String?.isNullOrEmpty(): Boolean = - this == null || this == "" || isEmpty() - -/** - * @return true if the string is null, empty string "", or the length is less than equal to 0 - */ -fun String?.isNotNullOrEmpty(): Boolean = - this != null && this != "" && isNotEmpty() - -fun StringBuilder.appendQuotedIfNeeded(string: String?) = apply { - if (string == "*") - return append(string) - - append(string.quoteIfNeeded()) - return this -} - -private val QUOTE = '`' -private val QUOTE_PATTERN: Pattern = (QUOTE + ".*" + QUOTE).toPattern() - -/** - * Helper method to check if name is quoted. - * - * @return true if the name is quoted. We may not want to quote something if its already so. - */ -fun String?.isQuoted(): Boolean = QUOTE_PATTERN.matcher(this).find() - -/** - * @param columnName The column name to use. - * @return A name in quotes. E.G. index => `index` so we can use keywords as column names without fear - * of clashing. - */ -fun String?.quote(): String = "${QUOTE}${this?.replace(".", "`.`")}${QUOTE}" - -fun String?.quoteIfNeeded() = if (this != null && !isQuoted()) { - quote() -} else { - this -} - -/** - * Appends the [SQLiteType] to [StringBuilder] - */ -fun StringBuilder.appendSQLiteType(sqLiteType: SQLiteType): StringBuilder = append(sqLiteType.name) - -/** - * Strips quotes out of a identifier if need to do so. - * - * @param name The name ot strip the quotes from. - * @return A non-quoted name. - */ -fun String?.stripQuotes(): String? { - var ret: String? = this - if (ret != null && ret.isQuoted()) { - ret = ret.replace("`", "") - } - return ret -} - - -/** - * Appends a value only if it's not empty or null - * - * @param name The name of the qualifier - * @param value The value to append after the name - * @return This instance - */ -fun StringBuilder.appendQualifier(name: String?, value: String?): StringBuilder { - if (value != null && value.isNotEmpty()) { - if (name != null) { - append(name) - } - append(" $value ") - } - return this -} - -/** - * Appends the value only if its not null - * - * @param value If not null, its string representation. - * @return This instance - */ -fun StringBuilder.appendOptional(value: Any?): StringBuilder { - if (value != null) { - append(value) - } - return this -} - -/** - * Appends an array of these objects by joining them with a comma with - * [.join] - * - * @param objects The array of objects to pass in - * @return This instance - */ -fun StringBuilder.appendArray(vararg objects: Any): StringBuilder = append(objects.joinToString()) - -/** - * Appends a list of objects by joining them with a comma with - * [.join] - * - * @param objects The list of objects to pass in - * @return This instance - */ -fun StringBuilder.appendList(objects: List<*>): StringBuilder = append(objects.joinToString()) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ColumnIgnore.kt b/core/src/main/kotlin/com/dbflow5/annotation/ColumnIgnore.kt deleted file mode 100644 index a75591c9cf814c72660c796a32e9d81b7c466590..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ColumnIgnore.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: An annotation used to ignore a column in the [Table.allFields] instance. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.FIELD) -annotation class ColumnIgnore diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ColumnMap.kt b/core/src/main/kotlin/com/dbflow5/annotation/ColumnMap.kt deleted file mode 100644 index 7ca70399e6ae69845c4465697370697bd97adacd..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ColumnMap.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Maps an arbitrary object and its corresponding fields into a set of columns. It is similar - * to [ForeignKey] except it's not represented in the DB hierarchy. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.FIELD) -annotation class ColumnMap( - /** - * Defines explicit references for a composite [ColumnMap] definition. - * - * @return override explicit usage of all fields and provide custom references. - */ - val references: Array = []) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ColumnMapReference.kt b/core/src/main/kotlin/com/dbflow5/annotation/ColumnMapReference.kt deleted file mode 100644 index 4d0a2aa76e7454193af21beeda1f6ef417d240d5..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ColumnMapReference.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.dbflow5.annotation - -import com.dbflow5.converter.TypeConverter -import kotlin.reflect.KClass - -/** - * Description: Allows a [ColumnMap] to specify a reference override for its fields. Anything not - * defined here will not be used. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.FIELD) -annotation class ColumnMapReference( - /** - * @return The local column name that will be referenced in the DB - */ - val columnName: String, - /** - * @return The column name in the referenced table - */ - val columnMapFieldName: String, - /** - * @return The default value for the reference column. Same as [Column.defaultValue] - */ - val defaultValue: String = "", - - /** - * @return A custom type converter that's only used for this field. It will be created and used in - * the Adapter associated with this table. - */ - val typeConverter: KClass> = TypeConverter::class, - - /** - * @return Specify the [NotNull] annotation here and it will get pasted into the reference definition. - */ - val notNull: NotNull = NotNull(onNullConflict = ConflictAction.NONE)) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Database.kt b/core/src/main/kotlin/com/dbflow5/annotation/Database.kt deleted file mode 100644 index 521a208361c58f9cb44e36f4e4be7a93f2742fa6..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/Database.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Creates a new database to use in the application. - * - * - * If we specify one DB, then all models do not need to specify a DB. As soon as we specify two, then each - * model needs to define what DB it points to. - * - * - * - * Models will specify which DB it belongs to, - * but they currently can only belong to one DB. - * - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -@Retention(AnnotationRetention.SOURCE) -annotation class Database( - /** - * @return The current version of the DB. Increment it to trigger a DB update. - */ - val version: Int, - /** - * @return If true, SQLite will throw exceptions when [ForeignKey] constraints are not respected. - * Default is false and will not throw exceptions. - */ - val foreignKeyConstraintsEnforced: Boolean = false, - /** - * @return Checks for consistency in the DB, if true it will recopy over the prepackage database. - */ - val consistencyCheckEnabled: Boolean = false, - /** - * @return Keeps a backup for whenever the database integrity fails a "PRAGMA quick_check(1)" that will - * replace the corrupted DB - */ - val backupEnabled: Boolean = false, - /** - * @return Global default insert conflict that can be applied to any table when it leaves - * its [ConflictAction] as NONE. - */ - val insertConflict: ConflictAction = ConflictAction.NONE, - /** - * @return Global update conflict that can be applied to any table when it leaves its - * [ConflictAction] as NONE - */ - val updateConflict: ConflictAction = ConflictAction.NONE) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKey.kt b/core/src/main/kotlin/com/dbflow5/annotation/ForeignKey.kt deleted file mode 100644 index ebd0353804190b1be9a2472026a070b122258ee6..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKey.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.dbflow5.annotation - -import kotlin.reflect.KClass - -/** - * Description: - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FIELD) -annotation class ForeignKey( - /** - * Defines explicit references for a composite [ForeignKey] definition. This is no longer required - * as the library will auto-generate references for you based on the other table's primary keys. - * - * @return the set of explicit references if you wish to have different values than default generated. - */ - val references: Array = [], - /** - * @return Default false. When this column is a [ForeignKey] and table object, - * returning true will save the model before adding the fields to save as a foreign key. - * If false, we expect the field to not change and must save the model manually outside - * of the ModelAdapter before saving the child class. - */ - val saveForeignKeyModel: Boolean = false, - /** - * @return Default false. When this column is a [ForeignKey] and table object, - * returning true will delte the model before deleting its enclosing child class. - * If false, we expect the field to not change and must delete the model manually outside - * of the ModelAdapter before saving the child class. - */ - val deleteForeignKeyModel: Boolean = false, - /** - * @return Replaces legacy ForeignKeyContainer, this method instructs the code generator to only - * populate the model with the [ForeignKeyReference] defined in this field. This skips - * the Select retrieval convenience. - */ - val stubbedRelationship: Boolean = false, - /** - * @return If true, during a transaction, FK constraints are not violated immediately until the resulting transaction commits. - * This is useful for out of order foreign key operations. - * @see [Deferred Foreign Key Constraints](http://www.sqlite.org/foreignkeys.html.fk_deferred) - */ - val deferred: Boolean = false, - /** - * @return an optional table class that this reference points to. It's only used if the field - * is NOT a Model class. - */ - val tableClass: KClass<*> = Any::class, - /** - * Defines [ForeignKeyAction] action to be performed - * on delete of referenced record. Defaults to [ForeignKeyAction.NO_ACTION]. Used only when - * columnType is [ForeignKey]. - * - * @return [ForeignKeyAction] - */ - val onDelete: ForeignKeyAction = ForeignKeyAction.NO_ACTION, - /** - * Defines [ForeignKeyAction] action to be performed - * on update of referenced record. Defaults to [ForeignKeyAction.NO_ACTION]. Used only when - * columnType is [ForeignKey]. - * - * @return [ForeignKeyAction] - */ - val onUpdate: ForeignKeyAction = ForeignKeyAction.NO_ACTION) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyReference.kt b/core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyReference.kt deleted file mode 100644 index 0430a075c4c47dacda9cdf2aa8ca285fbe8bbe1a..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ForeignKeyReference.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Used inside of [ForeignKey.references], describes the - * local column name, type, and referencing table column name. - * - * - * Note: the type of the local column must match the - * column type of the referenced column. By using a field as a Model object, - * you will need to ensure the same types are used. - */ -@Retention(AnnotationRetention.SOURCE) -annotation class ForeignKeyReference( - /** - * @return The local column name that will be referenced in the DB - */ - val columnName: String, - /** - * @return The column name in the referenced table - */ - val foreignKeyColumnName: String, - /** - * @return The default value for the reference column. Same as [Column.defaultValue] - */ - val defaultValue: String = "", - - /** - * @return Specify the [NotNull] annotation here and it will get pasted into the reference definition. - */ - val notNull: NotNull = NotNull(onNullConflict = ConflictAction.NONE)) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Index.kt b/core/src/main/kotlin/com/dbflow5/annotation/Index.kt deleted file mode 100644 index 1838a9af35446d096325ed8083a268b9ddbf6dae..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/Index.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Creates an index for a specified [Column]. A single column can belong to multiple - * indexes within the same table if you wish. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FIELD) -annotation class Index( - /** - * @return The set of index groups that this index belongs to. - */ - val indexGroups: IntArray = []) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/IndexGroup.kt b/core/src/main/kotlin/com/dbflow5/annotation/IndexGroup.kt deleted file mode 100644 index 172cd9e72e5cfd961efe91a38ff9fd9ebec3408c..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/IndexGroup.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.dbflow5.annotation - -const val INDEX_GENERIC = -1 - -/** - * Description: - */ -@Target(AnnotationTarget.ANNOTATION_CLASS) -@Retention(AnnotationRetention.SOURCE) -annotation class IndexGroup( - /** - * @return The number that each contained [Index] points to, so they can be combined into a single index. - * If [.INDEX_GENERIC], this will assume a generic index that covers the whole table. - */ - val number: Int = INDEX_GENERIC, - /** - * @return The name of this index. It must be unique from other [IndexGroup]. - */ - val name: String, - /** - * @return If true, this will disallow duplicate values to be inserted into the table. - */ - val unique: Boolean = false) - diff --git a/core/src/main/kotlin/com/dbflow5/annotation/InheritedColumn.kt b/core/src/main/kotlin/com/dbflow5/annotation/InheritedColumn.kt deleted file mode 100644 index 1c44bd4d16df9bc4aba0099e2e3d74e239ee7641..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/InheritedColumn.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5.annotation - -import com.dbflow5.annotation.ConflictAction - -/** - * Description: Allows [Table] to inherit fields from other objects to make it part of the DB table. - */ -@Target(AnnotationTarget.ANNOTATION_CLASS) -@Retention(AnnotationRetention.SOURCE) -annotation class InheritedColumn( - /** - * @return The column annotation as if it was part of the class - */ - val column: Column, - /** - * @return The field name that an inherited column uses. It must match exactly case-by-case to the field you're referencing. - * If the field is private, the [Column] allows you to define getter and setters for it. - */ - val fieldName: String, - /** - * @return If specified other than [ConflictAction.NONE], then we assume [NotNull]. - */ - val nonNullConflict: ConflictAction = ConflictAction.NONE) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/InheritedPrimaryKey.kt b/core/src/main/kotlin/com/dbflow5/annotation/InheritedPrimaryKey.kt deleted file mode 100644 index ba4f9b421e6d1d9264b41a778bc36ba59b33a587..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/InheritedPrimaryKey.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Allows you to specify a non-Column to be inherited and used as a [PrimaryKey] - */ -@Target(AnnotationTarget.ANNOTATION_CLASS) -@Retention(AnnotationRetention.SOURCE) -annotation class InheritedPrimaryKey( - /** - * @return The primary key annotation as if it was part of the class - */ - val primaryKey: PrimaryKey, - /** - * @return The column annotation as if it was part of the class - */ - val column: Column, - /** - * @return The field name that an inherited column uses. It must match exactly case-by-case to the field you're referencing. - * If the field is private, the [PrimaryKey] allows you to define getter and setters for it. - */ - val fieldName: String) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Migration.kt b/core/src/main/kotlin/com/dbflow5/annotation/Migration.kt deleted file mode 100644 index ccc5aeb7f47f5f03babfe1dc44e3e3979f4fcab4..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/Migration.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.dbflow5.annotation - -import kotlin.reflect.KClass - -/** - * Description: Marks a Migration class to be included in DB construction. The class using this annotation - * must implement the Migration interface. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class Migration( - /** - * @return The version the migration will trigger at. - */ - val version: Int, - /** - * @return Specify the database class that this migration belongs to. - */ - val database: KClass<*>, - /** - * @return If number greater than -1, the migrations are in run in reverse priority, - * meaning ones from the same [version] get ordered from - * lowest to highest number. if they are the same priority, - * there is no telling which one is executed first. The - * annotation processor will process in order it finds the classes. - */ - val priority: Int = -1) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ModelCacheField.kt b/core/src/main/kotlin/com/dbflow5/annotation/ModelCacheField.kt deleted file mode 100644 index e327ce8ca34846142cf9ebc6b21ff1f2f879f97c..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ModelCacheField.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: marks a single field as a ModelCache creator that is used in the corresponding ModelAdapter. - */ -@Target(AnnotationTarget.FIELD) -@Retention(AnnotationRetention.SOURCE) -annotation class ModelCacheField diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ModelView.kt b/core/src/main/kotlin/com/dbflow5/annotation/ModelView.kt deleted file mode 100644 index 2fbbab58eb6f0f51b28229a287f2f4c80fdbae29..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ModelView.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.dbflow5.annotation - -import com.dbflow5.sql.Query -import kotlin.reflect.KClass - -/** - * Author: andrewgrosner - * Description: Marks a class as being an SQL VIEW definition. It must extend BaseModelView and have - * a single public, static, final field that is annotated with [ModelViewQuery] and be a [Query]. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class ModelView( - /** - * @return The name of this view. Default is the class name. - */ - val name: String = "", - /** - * @return The class of the database this corresponds to. - */ - val database: KClass<*>, - /** - * @return When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . - * The only required annotated field becomes The [PrimaryKey] - * or [PrimaryKey.autoincrement]. - */ - val allFields: Boolean = true, - - /** - * @return If true, we throw away checks for column indexing and simply assume that the cursor returns - * all our columns in order. This may provide a slight performance boost. - */ - val orderedCursorLookUp: Boolean = false, - /** - * @return When true, we reassign the corresponding Model's fields to default values when loading - * from cursor. If false, we assign values only if present in Cursor. - */ - val assignDefaultValuesFromCursor: Boolean = true, - /** - * @return The higher the number, the order by which the creation of this class gets called. - * Useful for creating ones that depend on another [ModelView]. - */ - val priority: Int = 0, - - /** - * @return When false, this view gets generated and associated with database, however it will not immediately - * get created upon startup. This is useful for keeping around legacy tables for migrations. - */ - val createWithDatabase: Boolean = true) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/ModelViewQuery.kt b/core/src/main/kotlin/com/dbflow5/annotation/ModelViewQuery.kt deleted file mode 100644 index a7419c2fabfebc057a909fe4c62492aaa6c458ab..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/ModelViewQuery.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.dbflow5.annotation - -import com.dbflow5.sql.Query - -/** - * Description: Represents a field that is a [Query]. This is only meant to be used as a query - * reference in [ModelView]. This is so the annotation processor knows how to access the query of - * the view. - */ -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) -@Retention(AnnotationRetention.SOURCE) -annotation class ModelViewQuery diff --git a/core/src/main/kotlin/com/dbflow5/annotation/MultipleManyToMany.kt b/core/src/main/kotlin/com/dbflow5/annotation/MultipleManyToMany.kt deleted file mode 100644 index 659c32c2ce3768aa0f609bc96e344cb3ef04ea57..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/MultipleManyToMany.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Provides ability to add multiple [ManyToMany] annotations at once. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class MultipleManyToMany(vararg val value: ManyToMany) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/OneToMany.kt b/core/src/main/kotlin/com/dbflow5/annotation/OneToMany.kt deleted file mode 100644 index 02580330e3f356f4739eddd19b6fb1e01f92b05e..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/OneToMany.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Describes a 1-many relationship. It applies to some method that returns a [List] of Model objects. - * This annotation can handle loading, deleting, and saving when the current data changes. By default it will call the - * associated method when the containing class operates. - */ -@Target(AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER) -@Retention(AnnotationRetention.SOURCE) -annotation class OneToMany( - /** - * @return The methods you wish to call it from. By default it's loaded out of the DB. - */ - val oneToManyMethods: Array = [(OneToManyMethod.LOAD)], - /** - * @return The name of the list variable to use. If is left blank, we will remove the "get" and then decapitalize the remaining name. - */ - val variableName: String = "", - /** - * @return If true, the code generated for this relationship done as efficiently as possible. - * It will not work on nested relationships, caching, and other code that requires overriding of BaseModel or Model operations. - */ - val efficientMethods: Boolean = true) - -/** - * The method to apply the OneToMany to. - */ -enum class OneToManyMethod { - - /** - * Load this relationship when the parent model loads from the database. This is called before the OnLoadFromCursor - * method, but after other columns load. - */ - LOAD, - - /** - * Inserts code to delete the results returned from the List relationship when the parent model is deleted. - */ - DELETE, - - /** - * Inserts code to save the list of models when the parent model is saved. - */ - SAVE, - - /** - * Shorthand to support all options. - */ - ALL -} - diff --git a/core/src/main/kotlin/com/dbflow5/annotation/PrimaryKey.kt b/core/src/main/kotlin/com/dbflow5/annotation/PrimaryKey.kt deleted file mode 100644 index 103a7cace0f90f4dea294ba4c4c66fac3fe2eb25..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/PrimaryKey.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FIELD) -annotation class PrimaryKey( - /** - * Specifies if the column is autoincrementing or not - */ - val autoincrement: Boolean = false, - /** - * Specifies the column to be treated as a ROWID but is not an [.autoincrement]. This - * overrides [.autoincrement] and is mutually exclusive. - */ - val rowID: Boolean = false, - /** - * @return When true, we simple do {columnName} > 0 when checking for it's existence if [.autoincrement] - * is true. If not, we do a full database SELECT exists. - */ - val quickCheckAutoIncrement: Boolean = false) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/QueryModel.kt b/core/src/main/kotlin/com/dbflow5/annotation/QueryModel.kt deleted file mode 100644 index ce8a5da037cca7c06c56fb5acbc76da0f3440cba..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/QueryModel.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.dbflow5.annotation - -import kotlin.reflect.KClass - -/** - * Description: Marks a Model class as NOT a [Table], but generates code for retrieving data from a - * generic query - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class QueryModel( - /** - * @return Specify the class of the database to use. - */ - val database: KClass<*>, - /** - * @return If true, all accessible, non-static, and non-final fields are treated as valid fields. - * @see Table.allFields - */ - val allFields: Boolean = true, - - /** - * @return If true, we throw away checks for column indexing and simply assume that the cursor returns - * all our columns in order. This may provide a slight performance boost. - */ - val orderedCursorLookUp: Boolean = false, - /** - * @return When true, we reassign the corresponding Model's fields to default values when loading - * from cursor. If false, we assign values only if present in Cursor. - */ - val assignDefaultValuesFromCursor: Boolean = true) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Table.kt b/core/src/main/kotlin/com/dbflow5/annotation/Table.kt deleted file mode 100644 index 4b71dad1e35c0d4f784461a0cad1e8876742b76c..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/Table.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.dbflow5.annotation - -import com.dbflow5.annotation.ConflictAction -import kotlin.reflect.KClass - -val DEFAULT_CACHE_SIZE = 25 - -/** - * Author: andrewgrosner - * Description: Marks a class as being a table for only ONE DB. It must implement the Model interface and all fields MUST be package private. - * This will generate a $Table and $Adapter class. The $Table class generates static final column name variables to reference in queries. - * The $Adapter class defines how to retrieve and store this object as well as other methods for acting on model objects in the database. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS) -annotation class Table( - /** - * @return Specifies a different name for the table than the name of the Model class. - */ - val name: String = "", - /** - * @return Specify the database class that this table belongs to. It must have the [Database] annotation. - */ - val database: KClass<*>, - /** - * @return Specify the general conflict algorithm used by this table when updating records. - */ - val updateConflict: com.dbflow5.annotation.ConflictAction = com.dbflow5.annotation.ConflictAction.NONE, - /** - * @return Specify the general insert conflict algorithm used by this table. - */ - val insertConflict: com.dbflow5.annotation.ConflictAction = com.dbflow5.annotation.ConflictAction.NONE, - /** - * @return An optional [ConflictAction] that we append to creation for conflict handling in PK. - */ - val primaryKeyConflict: com.dbflow5.annotation.ConflictAction = com.dbflow5.annotation.ConflictAction.NONE, - /** - * @return When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . - * The only required annotated field becomes The [PrimaryKey] - * or [PrimaryKey.autoincrement]. - */ - val allFields: Boolean = true, - /** - * @return If true, all private boolean fields will use "is" instead of "get" for its getter and - * "set" without the "is" if it starts with "is" - */ - val useBooleanGetterSetters: Boolean = true, - /** - * @return If true, caching mechanism is enabled. This works for single primary key tables. For - * multi-primary key tables, IMultiKeyCacheModel interface is required to specify the caching key. - */ - val cachingEnabled: Boolean = false, - /** - * @return If true, we throw away checks for column indexing and simply assume that the cursor returns - * all our columns in order. This may provide a slight performance boost. - */ - val orderedCursorLookUp: Boolean = false, - /** - * @return When true, we reassign the corresponding Model's fields to default values when loading - * from cursor. If false, we assign values only if present in Cursor. - */ - val assignDefaultValuesFromCursor: Boolean = true, - /** - * @return When false, this table gets generated and associated with database, however it will not immediately - * get created upon startup. This is useful for keeping around legacy tables for migrations. - */ - val createWithDatabase: Boolean = true, - - /** - * If true this table will be created as a TEMP table. Pair this with [createWithDatabase] - * to properly not create the table. - */ - val temporary: Boolean = false, - - /** - * If true, generates ContentValues bindings for a table. By default it no longer does. - */ - val generateContentValues: Boolean = false, - /** - * @return The cache size for this Table. - */ - val cacheSize: Int = 25, - /** - * @return Declares a set of UNIQUE columns with the corresponding [ConflictAction]. A [Column] - * will point to this group using [Unique.uniqueGroups] - */ - val uniqueColumnGroups: Array = [], - /** - * @return The set of INDEX clauses that specific columns can define to belong to, using the [Index] annotation. - * The generated Index properties belong to the corresponding property class to this table. - */ - val indexGroups: Array = [], - /** - * @return A set of inherited accessible fields not necessarily defined as columns in the super class of this table. - * Each must be accessible via: public, package private, or protected or getter/setters. - */ - val inheritedColumns: Array = [], - /** - * @return A set of inherited accessible fields not necessarily defined as columns in the super class of this table. - * Each must be accessible via: public, package private, or protected or getter/setters. - */ - val inheritedPrimaryKeys: Array = []) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/Unique.kt b/core/src/main/kotlin/com/dbflow5/annotation/Unique.kt deleted file mode 100644 index e0c9b456aef6e2f396ae576ee4e8bb4483a7627a..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/Unique.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: Marks the field as unique, meaning its value cannot be repeated. It is, however, - * NOT a primary key. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.FIELD) -annotation class Unique( - /** - * @return if field is unique. If false, we expect [.uniqueGroups] to be specified.` - */ - val unique: Boolean = true, - /** - * @return Marks a unique field as part of a unique group. For every unique number entered, - * it will generate a UNIQUE() column statement. - */ - val uniqueGroups: IntArray = [], - /** - * Defines how to handle conflicts for a unique column - * - * @return a [ConflictAction] enum - */ - val onUniqueConflict: ConflictAction = ConflictAction.FAIL) diff --git a/core/src/main/kotlin/com/dbflow5/annotation/UniqueGroup.kt b/core/src/main/kotlin/com/dbflow5/annotation/UniqueGroup.kt deleted file mode 100644 index 4667558cb6aad2b191fa9977865585ec93f66acb..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/annotation/UniqueGroup.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.dbflow5.annotation - -/** - * Description: - */ -@Target(AnnotationTarget.ANNOTATION_CLASS) -@Retention(AnnotationRetention.SOURCE) -annotation class UniqueGroup( - /** - * @return The number that columns point to to use this group - */ - val groupNumber: Int, - /** - * @return The conflict action that this group takes. - */ - val uniqueConflict: ConflictAction = ConflictAction.FAIL) diff --git a/core/src/main/kotlin/com/dbflow5/converter/TypeConverters.kt b/core/src/main/kotlin/com/dbflow5/converter/TypeConverters.kt deleted file mode 100644 index 5292afbcee336d4bd17a13afbad075c95d96a7a9..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/converter/TypeConverters.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.dbflow5.converter - -import java.math.BigDecimal -import java.math.BigInteger -import java.sql.Time -import java.sql.Timestamp -import java.util.* - -/** - * Author: andrewgrosner - * Description: This class is responsible for converting the stored database value into the field value in - * a Model. - */ -@com.dbflow5.annotation.TypeConverter -abstract class TypeConverter { - - /** - * Converts the ModelClass into a DataClass - * - * @param model this will be called upon syncing - * @return The DataClass value that converts into a SQLite type - */ - abstract fun getDBValue(model: ModelClass?): DataClass? - - /** - * Converts a DataClass from the DB into a ModelClass - * - * @param data This will be called when the model is loaded from the DB - * @return The ModelClass value that gets set in a Model that holds the data class. - */ - abstract fun getModelValue(data: DataClass?): ModelClass? -} - -/** - * Description: Defines how we store and retrieve a [java.math.BigDecimal] - */ -@com.dbflow5.annotation.TypeConverter -class BigDecimalConverter : TypeConverter() { - override fun getDBValue(model: BigDecimal?): String? = model?.toString() - - override fun getModelValue(data: String?): BigDecimal? = if (data == null) null else BigDecimal(data) -} - -/** - * Description: Defines how we store and retrieve a [java.math.BigInteger] - */ -@com.dbflow5.annotation.TypeConverter -class BigIntegerConverter : TypeConverter() { - override fun getDBValue(model: BigInteger?): String? = model?.toString() - - override fun getModelValue(data: String?): BigInteger? = if (data == null) null else BigInteger(data) -} - -/** - * Description: Converts a boolean object into an Integer for database storage. - */ -@com.dbflow5.annotation.TypeConverter -class BooleanConverter : TypeConverter() { - override fun getDBValue(model: Boolean?): Int? = if (model == null) null else if (model) 1 else 0 - - override fun getModelValue(data: Int?): Boolean? = if (data == null) null else data == 1 -} - -/** - * Description: Defines how we store and retrieve a [java.util.Calendar] - */ -@com.dbflow5.annotation.TypeConverter(allowedSubtypes = [(GregorianCalendar::class)]) -class CalendarConverter : TypeConverter() { - - override fun getDBValue(model: Calendar?): Long? = model?.timeInMillis - - override fun getModelValue(data: Long?): Calendar? = - if (data != null) Calendar.getInstance().apply { timeInMillis = data } else null -} - -/** - * Description: Converts a [Character] into a [String] for database storage. - */ -@com.dbflow5.annotation.TypeConverter -class CharConverter : TypeConverter() { - - override fun getDBValue(model: Char?): String? = - if (model != null) String(charArrayOf(model)) else null - - override fun getModelValue(data: String?): Char? = if (data != null) data[0] else null -} - -/** - * Description: Defines how we store and retrieve a [java.util.Date] - */ -@com.dbflow5.annotation.TypeConverter -class DateConverter : TypeConverter() { - - override fun getDBValue(model: Date?): Long? = model?.time - - override fun getModelValue(data: Long?): Date? = if (data == null) null else Date(data) -} - -/** - * Description: Defines how we store and retrieve a [java.sql.Date] - */ -@com.dbflow5.annotation.TypeConverter(allowedSubtypes = [(Time::class), (Timestamp::class)]) -class SqlDateConverter : TypeConverter() { - - override fun getDBValue(model: java.sql.Date?): Long? = model?.time - - override fun getModelValue(data: Long?): java.sql.Date? = if (data == null) null else java.sql.Date(data) -} - -/** - * Description: Responsible for converting a [UUID] to a [String]. - * - * @author Andrew Grosner (fuzz) - */ -@com.dbflow5.annotation.TypeConverter -class UUIDConverter : TypeConverter() { - - override fun getDBValue(model: UUID?): String? = model?.toString() - - override fun getModelValue(data: String?): UUID? { - return if (data == null) { - null - } else UUID.fromString(data) - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/dbflow5/data/Blob.kt b/core/src/main/kotlin/com/dbflow5/data/Blob.kt deleted file mode 100644 index 9e668070f79a201b967f593bac0cd46addf102c3..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/data/Blob.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.dbflow5.data - -/** - * Description: Provides a way to support blob format data. - */ -class Blob @JvmOverloads constructor( - /** - * Sets the underlying blob data. - * - * @param blob The set of bytes to use. - */ - var blob: ByteArray? = null) diff --git a/core/src/main/kotlin/com/dbflow5/sql/QueryCloneable.kt b/core/src/main/kotlin/com/dbflow5/sql/QueryCloneable.kt deleted file mode 100644 index a137de4eb081c1e694843b2846bbb5ef79ea8c9d..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/sql/QueryCloneable.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.dbflow5.sql - -/** - * Description: - */ -interface QueryCloneable { - - fun cloneSelf(): T -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/dbflow5/sql/SQLiteType.kt b/core/src/main/kotlin/com/dbflow5/sql/SQLiteType.kt deleted file mode 100644 index 2b4b90d8168b8545f80792da00537218f6bd9a22..0000000000000000000000000000000000000000 --- a/core/src/main/kotlin/com/dbflow5/sql/SQLiteType.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.dbflow5.sql - -import com.dbflow5.data.Blob - -/** - * Description: Represents a type that SQLite understands. - */ -enum class SQLiteType { - - /** - * Represents an integer number in the DB. - */ - INTEGER, - - /** - * Represents a floating-point, precise number. - */ - REAL, - - /** - * Represents text. - */ - TEXT, - - /** - * A column defined by [byte[]] data. Usually reserved for images or larger pieces of content. - */ - BLOB; - - - companion object { - - private val sTypeMap = hashMapOf( - Byte::class.javaPrimitiveType!!.name to INTEGER, - Short::class.javaPrimitiveType!!.name to INTEGER, - Int::class.javaPrimitiveType!!.name to INTEGER, - Long::class.javaPrimitiveType!!.name to INTEGER, - Float::class.javaPrimitiveType!!.name to REAL, - Double::class.javaPrimitiveType!!.name to REAL, - Boolean::class.javaPrimitiveType!!.name to INTEGER, - Char::class.javaPrimitiveType!!.name to TEXT, - ByteArray::class.java.name to BLOB, - Byte::class.java.name to INTEGER, - Short::class.java.name to INTEGER, - Int::class.java.name to INTEGER, - Long::class.java.name to INTEGER, - Float::class.java.name to REAL, - Double::class.java.name to REAL, - Boolean::class.java.name to INTEGER, - Char::class.java.name to TEXT, - CharSequence::class.java.name to TEXT, - String::class.java.name to TEXT, - Array::class.java.name to BLOB, - Blob::class.java.name to BLOB) - - /** - * Returns the [SQLiteType] for this class - * - * @param className The fully qualified class name - * @return The type from the class name - */ - - operator fun get(className: String): SQLiteType? = sTypeMap[className] - - fun containsClass(className: String): Boolean = sTypeMap.containsKey(className) - } -} diff --git a/coroutines/build.gradle.kts b/coroutines/build.gradle.kts deleted file mode 100644 index dd9cb3e2be4dba9afd5d84c1086d3df6dd9e58c8..0000000000000000000000000000000000000000 --- a/coroutines/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} - -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - defaultConfig { - minSdkVersion(15) - targetSdkVersion(Versions.TargetSdk) - } - - buildTypes { - getByName("release") { - minifyEnabled (false) - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") - } - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - implementation(project(":lib")) - api(Dependencies.Coroutines) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/coroutines/gradle.properties b/coroutines/gradle.properties deleted file mode 100644 index b4da7dfc784c266b9e9feb559325268586906124..0000000000000000000000000000000000000000 --- a/coroutines/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-coroutines -bt_packaging=aar -bt_artifact_id=dbflow-coroutines \ No newline at end of file diff --git a/coroutines/proguard-rules.pro b/coroutines/proguard-rules.pro deleted file mode 100644 index f1b424510da51fd82143bc74a0a801ae5a1e2fcd..0000000000000000000000000000000000000000 --- a/coroutines/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/coroutines/settings.gradle b/coroutines/settings.gradle deleted file mode 100644 index 00126c83cd98c292680fc4b1685a6e5d292fb7c1..0000000000000000000000000000000000000000 --- a/coroutines/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = buildName \ No newline at end of file diff --git a/coroutines/src/main/AndroidManifest.xml b/coroutines/src/main/AndroidManifest.xml deleted file mode 100644 index 88396f77d30e5146ca6596d6f76dc61e10f5f874..0000000000000000000000000000000000000000 --- a/coroutines/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/coroutines/src/main/kotlin/com/dbflow5/coroutines/Coroutines.kt b/coroutines/src/main/kotlin/com/dbflow5/coroutines/Coroutines.kt deleted file mode 100644 index 58f450a7cb13ebbd5082c003f79d04bea98e88e8..0000000000000000000000000000000000000000 --- a/coroutines/src/main/kotlin/com/dbflow5/coroutines/Coroutines.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.dbflow5.coroutines - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.query.Queriable -import com.dbflow5.structure.delete -import com.dbflow5.structure.insert -import com.dbflow5.structure.load -import com.dbflow5.structure.save -import com.dbflow5.structure.update -import com.dbflow5.transaction.FastStoreModelTransaction -import com.dbflow5.transaction.Transaction -import com.dbflow5.transaction.fastDelete -import com.dbflow5.transaction.fastInsert -import com.dbflow5.transaction.fastSave -import com.dbflow5.transaction.fastUpdate -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** - * Turns this [Transaction.Builder] into a [Deferred] object to use in coroutines. - */ -fun Transaction.Builder.defer(): Deferred { - val deferred = CompletableDeferred() - val transaction = success { _, result -> deferred.complete(result) } - .error { _, throwable -> deferred.completeExceptionally(throwable) } - .build() - deferred.invokeOnCompletion { - if (deferred.isCancelled) { - transaction.cancel() - } - } - transaction.execute() - return deferred -} - -inline fun constructCoroutine(continuation: CancellableContinuation, - databaseDefinition: DBFlowDatabase, - crossinline fn: () -> R) { - val transaction = databaseDefinition.beginTransactionAsync { fn() } - .success { _, result -> continuation.resume(result) } - .error { _, throwable -> - if (continuation.isCancelled) return@error - continuation.resumeWithException(throwable) - }.build() - transaction.execute() - - continuation.invokeOnCancellation { - if (continuation.isCancelled) { - transaction.cancel() - } - } -} - - -/** - * Description: Puts this [Queriable] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun Q.awaitTransact( - dbFlowDatabase: DBFlowDatabase, - crossinline queriableFunction: Q.(DBFlowDatabase) -> R) = suspendCancellableCoroutine { continuation -> - com.dbflow5.coroutines.constructCoroutine(continuation, dbFlowDatabase) { queriableFunction(dbFlowDatabase) } -} - -/** - * Description: Puts a [Model] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun M.awaitSave(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructCoroutine(continuation, databaseDefinition) { save(databaseDefinition) } -} - -/** - * Description: Puts a [Model] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun M.awaitInsert(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructCoroutine(continuation, databaseDefinition) { insert(databaseDefinition) } -} - -/** - * Description: Puts a [Model] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun M.awaitDelete(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructCoroutine(continuation, databaseDefinition) { delete(databaseDefinition) } -} - -/** - * Description: Puts a [Model] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun M.awaitUpdate(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructCoroutine(continuation, databaseDefinition) { update(databaseDefinition) } -} - -/** - * Description: Puts a [Model] operation inside a coroutine. Inside the [queriableFunction] - * execute the db operation. - */ -suspend inline fun M.awaitLoad(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructCoroutine(continuation, databaseDefinition) { load(databaseDefinition) } -} - -/** - * Description: Puts the [Collection] inside a [FastStoreModelTransaction] coroutine. - */ -suspend inline fun > M.awaitSave(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructFastCoroutine(continuation, databaseDefinition) { fastSave() } -} - -/** - * Description: Puts the [Collection] inside a [FastStoreModelTransaction] coroutine. - */ -suspend inline fun > M.awaitInsert(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructFastCoroutine(continuation, databaseDefinition) { fastInsert() } -} - -/** - * Description: Puts the [Collection] inside a [FastStoreModelTransaction] coroutine. - */ -suspend inline fun > M.awaitUpdate(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructFastCoroutine(continuation, databaseDefinition) { fastUpdate() } -} - -/** - * Description: Puts the [Collection] inside a [FastStoreModelTransaction] coroutine. - */ -suspend inline fun > M.awaitDelete(databaseDefinition: DBFlowDatabase) = suspendCancellableCoroutine { continuation -> - constructFastCoroutine(continuation, databaseDefinition) { fastDelete() } -} - - -inline fun constructFastCoroutine(continuation: CancellableContinuation, - databaseDefinition: DBFlowDatabase, - crossinline fn: () -> FastStoreModelTransaction.Builder) { - val transaction = databaseDefinition.beginTransactionAsync(fn().build()) - .success { _, result -> continuation.resume(result) } - .error { _, throwable -> - if (continuation.isCancelled) return@error - continuation.resumeWithException(throwable) - }.build() - transaction.execute() - - continuation.invokeOnCancellation { - transaction.cancel() - } -} \ No newline at end of file diff --git a/dbflow_banner.png b/dbflow_banner.png deleted file mode 100644 index 47cd4cf7b1ca3c36f1b80c6da67af4e6e980324e..0000000000000000000000000000000000000000 Binary files a/dbflow_banner.png and /dev/null differ diff --git a/coroutines/.gitignore b/entry/.gitignore similarity index 100% rename from coroutines/.gitignore rename to entry/.gitignore diff --git a/entry/build.gradle b/entry/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..58634d6d68b4d9d8f1793ba9b144ddb9fe1b9e2c --- /dev/null +++ b/entry/build.gradle @@ -0,0 +1,51 @@ +apply plugin: 'com.huawei.ohos.hap' +apply plugin: 'com.huawei.ohos.decctest' +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + + compileOptions { + annotationEnabled true + } + + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) + testImplementation 'junit:junit:4.13' + ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.100' + + implementation(project(":lib")) + implementation(project(":sqlcipher")) + implementation(project(":reactive-streams")) + implementation(project(":contentprovider")) + implementation(project(":paging")) + implementation(project(":livedata")) + +// implementation(project(":processor")) +// annotationProcessor(project(":processor")) + + //ohosTestAnnotationProcessor(project(":processor")) + //ohosTestImplementation(project(":processor")) + + testImplementation 'org.mockito:mockito-core:2.23.0' + ohosTestImplementation 'org.mockito:mockito-core:2.23.0' + ohosTestImplementation('org.mockito:mockito-android:2.23.0') + + ohosTestImplementation("org.glassfish:javax.annotation:10.0-b28") +} +decc { + supportType = ['html','xml'] +} + diff --git a/entry/proguard-rules.pro b/entry/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/entry/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/entry/src/main/config.json b/entry/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..76c2101e7f8b1951bb5b0887c3bc2557e3947222 --- /dev/null +++ b/entry/src/main/config.json @@ -0,0 +1,50 @@ +{ + "app": { + "bundleName": "com.dbflow5", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + }, + "apiVersion": { + "compatible": 5, + "target": 5, + "releaseType": "Release" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.dbflow5", + "name": ".DemoApp", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry", + "installationFree": true + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "name": "com.dbflow5.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/java/com/dbflow5/DemoApp.java b/entry/src/main/java/com/dbflow5/DemoApp.java new file mode 100644 index 0000000000000000000000000000000000000000..ed4705ce5fe21924eba5bbe8cb771fae98ec121a --- /dev/null +++ b/entry/src/main/java/com/dbflow5/DemoApp.java @@ -0,0 +1,14 @@ +package com.dbflow5; + +import ohos.aafwk.ability.AbilityPackage; +import ohos.app.Context; + +public class DemoApp extends AbilityPackage { + public static Context context; + + @Override + public void onInitialize() { + super.onInitialize(); + context = this; + } +} \ No newline at end of file diff --git a/entry/src/main/java/com/dbflow5/MainAbility.java b/entry/src/main/java/com/dbflow5/MainAbility.java new file mode 100644 index 0000000000000000000000000000000000000000..523322b62cdbc50b129c1480be519d49d62bd5dd --- /dev/null +++ b/entry/src/main/java/com/dbflow5/MainAbility.java @@ -0,0 +1,13 @@ +package com.dbflow5; + +import com.dbflow5.slice.MainAbilitySlice; +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; + +public class MainAbility extends Ability { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(MainAbilitySlice.class.getName()); + } +} diff --git a/entry/src/main/java/com/dbflow5/StubContentProvider.java b/entry/src/main/java/com/dbflow5/StubContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..a3f350c617415ab09496f62e1025c89a50a0ead0 --- /dev/null +++ b/entry/src/main/java/com/dbflow5/StubContentProvider.java @@ -0,0 +1,11 @@ +package com.dbflow5; + +import ohos.aafwk.ability.DataAbilityHelper; + +/** + * Description: Used as a stub, include this in order to work around Android O changes to [ContentProvider] + */ +public class StubContentProvider { + + +} \ No newline at end of file diff --git a/entry/src/main/java/com/dbflow5/slice/MainAbilitySlice.java b/entry/src/main/java/com/dbflow5/slice/MainAbilitySlice.java new file mode 100644 index 0000000000000000000000000000000000000000..6a6d89d9d0abd1ece9c3df6b6bcfa3e8a5017f60 --- /dev/null +++ b/entry/src/main/java/com/dbflow5/slice/MainAbilitySlice.java @@ -0,0 +1,24 @@ +package com.dbflow5.slice; + +import com.dbflow5.ResourceTable; +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; + +public class MainAbilitySlice extends AbilitySlice { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_ability_main); + + } + + @Override + public void onActive() { + super.onActive(); + } + + @Override + public void onForeground(Intent intent) { + super.onForeground(intent); + } +} diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..815576c0c9bf3420a10a81a3df419121f6fbe242 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "app_name", + "value": "DBFlow" + }, + { + "name": "mainability_description", + "value": "Java_Phone_Empty Feature Ability" + }, + { + "name": "mainability_HelloWorld", + "value": "Hello World" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/background_ability_main.xml b/entry/src/main/resources/base/graphic/background_ability_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..c0c0a3df480fa387a452b9c40ca191cc918a3fc0 --- /dev/null +++ b/entry/src/main/resources/base/graphic/background_ability_main.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/ability_main.xml b/entry/src/main/resources/base/layout/ability_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..5c05fa29419366749440a0b838ad092fe0cdbad1 --- /dev/null +++ b/entry/src/main/resources/base/layout/ability_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/entry/src/main/resources/base/media/icon.png differ diff --git a/entry/src/ohosTest/config.json b/entry/src/ohosTest/config.json new file mode 100644 index 0000000000000000000000000000000000000000..7e8671602926a8f1afe646cb3202e937954b7335 --- /dev/null +++ b/entry/src/ohosTest/config.json @@ -0,0 +1,52 @@ +{ + "app": { + "bundleName": "com.dbflow5", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + }, + "apiVersion": { + "compatible": 5, + "target": 5, + "releaseType": "Release" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.dbflow5", + "name": ".DemoApp", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry_test", + "moduleType": "feature", + "installationFree": true + }, + "abilities": [ + { + "name": "decc.testkit.runner.EntryAbility", + "description": "Test Entry Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "launchType": "standard", + "orientation": "landscape", + "visible": true, + "type": "page" + } + ], + "reqPermissions": [ + { + "name": "com.dbflow5.DataAbility.DATA" + }, + { + "name": "ohos.permission.READ_USER_STORAGE" + }, + { + "name": "ohos.permission.WRITE_USER_STORAGE" + } + ] + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/dbflow5/BaseUnitTest.java b/entry/src/ohosTest/java/com/dbflow5/BaseUnitTest.java new file mode 100644 index 0000000000000000000000000000000000000000..965520a3b45e1ee8a683bb492d6e447fc79347ca --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/BaseUnitTest.java @@ -0,0 +1,22 @@ +package com.dbflow5; + +import decc.testkit.runner.HarmonyJUnitClassRunner; +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import ohos.app.Context; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(HarmonyJUnitClassRunner.class) +public abstract class BaseUnitTest { + @Rule + public DBFlowInstrumentedTestRule dblflowTestRule; + + public BaseUnitTest() { + dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> null); + } + + public final Context getContext() { + return AbilityDelegatorRegistry.getAbilityDelegator().getAppContext(); + } + +} diff --git a/entry/src/ohosTest/java/com/dbflow5/DBFlowInstrumentedTestRule.java b/entry/src/ohosTest/java/com/dbflow5/DBFlowInstrumentedTestRule.java new file mode 100644 index 0000000000000000000000000000000000000000..06c6c9234c71b31380636131bbc27dfaf0ec3a02 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/DBFlowInstrumentedTestRule.java @@ -0,0 +1,54 @@ +package com.dbflow5; + +import com.dbflow5.config.FlowConfig; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.OhosSQLiteOpenHelper; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.function.Function; + +/** + * DBFlowInstrumentedTestRule + * + * @author wangyin + * @since 2021/06/19 + */ +public class DBFlowInstrumentedTestRule implements TestRule { + private final Function dbConfigBlock; + + public DBFlowInstrumentedTestRule(Function dbConfigBlock) { + this.dbConfigBlock = dbConfigBlock; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); + FlowManager.init(DemoApp.context, builder -> { + builder.database(TestDatabase.class, builder1 -> { + builder1.transactionManagerCreator(ImmediateTransactionManager::new); + return null; + }, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + dbConfigBlock.apply(builder); + return null; + }); + + try { + base.evaluate(); + } finally { + FlowManager.destroy(); + } + } + }; + } + + public static DBFlowInstrumentedTestRule create(Function fn) { + return new DBFlowInstrumentedTestRule(fn); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/ImmediateTransactionManager.java b/entry/src/ohosTest/java/com/dbflow5/ImmediateTransactionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..691710263dfe7bdfad142dcfe6ec4584dc7098a1 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/ImmediateTransactionManager.java @@ -0,0 +1,36 @@ +package com.dbflow5; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.transaction.BaseTransactionManager; +import com.dbflow5.transaction.ITransactionQueue; +import com.dbflow5.transaction.Transaction; + + +/** + * ImmediateTransactionManager + * + * @author wangyin + * @since 2021/06/19 + */ +public class ImmediateTransactionManager extends BaseTransactionManager { + public ImmediateTransactionManager(DBFlowDatabase databaseDefinition) { + super(new ImmediateTransactionQueue(), databaseDefinition); + } +} +class ImmediateTransactionQueue implements ITransactionQueue{ + public void add(Transaction transaction) { + transaction.newBuilder().runCallbacksOnSameThread(true).build().executeSync(); + } + + public void cancel(Transaction transaction) { + } + + public void startIfNotAlive() { + } + + public void cancel(String name) { + } + + public void quit() { + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/SimpleForeignModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/SimpleForeignModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..22d1be08dc03082de5a134334d2dcca8dbbebb5a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/SimpleForeignModel_Table.java @@ -0,0 +1,154 @@ +package com.dbflow5; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class SimpleForeignModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(TestForeignKeyDatabase.SimpleForeignModel.class, "id"); + + /** + * Foreign Key */ + public static final Property model_name = new Property(TestForeignKeyDatabase.SimpleForeignModel.class, "model_name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,model_name}; + + public SimpleForeignModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TestForeignKeyDatabase.SimpleForeignModel.class; + } + + @Override + public final String getName() { + return "SimpleForeignModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "model_name": { + return model_name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleForeignModel model) { + statement.bindLong(1, (long)model.getId()); + if (model.getModel() != null) { + if (model.getModel().getName() != null) { + statement.bindString(2, model.getModel().getName()); + } else { + statement.bindString(2, ""); + } + } else { + statement.bindNull(2); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleForeignModel model) { + statement.bindLong(1, (long)model.getId()); + if (model.getModel() != null) { + if (model.getModel().getName() != null) { + statement.bindString(2, model.getModel().getName()); + } else { + statement.bindString(2, ""); + } + } else { + statement.bindNull(2); + } + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleForeignModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleForeignModel(id,model_name) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleForeignModel(id,model_name) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleForeignModel SET id=?,model_name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleForeignModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleForeignModel(id INTEGER, model_name TEXT, PRIMARY KEY(id), FOREIGN KEY(model_name) REFERENCES SimpleModel (name) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final TestForeignKeyDatabase.SimpleForeignModel loadFromCursor(FlowCursor cursor, + DatabaseWrapper wrapper) { + TestForeignKeyDatabase.SimpleForeignModel model = new TestForeignKeyDatabase.SimpleForeignModel(0, null); + model.setId(cursor.getIntOrDefault("id")); + int index_model_name_SimpleModel_Table = cursor.getColumnIndexForName("model_name"); + if (index_model_name_SimpleModel_Table != -1 && !cursor.isColumnNull(index_model_name_SimpleModel_Table)) { + model.setModel(com.dbflow5.query.SQLite.select().from(TestForeignKeyDatabase.SimpleModel.class).where() + .and(SimpleModel_Table.name.eq(cursor.getString(index_model_name_SimpleModel_Table))) + .querySingle(wrapper)); + } else { + model.setModel(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause( + TestForeignKeyDatabase.SimpleForeignModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/SimpleModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/SimpleModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..113b948ff0751b4c746d37782a969e4f0fc8217c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/SimpleModel_Table.java @@ -0,0 +1,135 @@ +package com.dbflow5; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class SimpleModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(TestForeignKeyDatabase.SimpleModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public SimpleModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TestForeignKeyDatabase.SimpleModel.class; + } + + @Override + public final String getName() { + return "SimpleModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "`name`": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + TestForeignKeyDatabase.SimpleModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleModel(name) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleModel(name) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleModel SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleModel(name TEXT, PRIMARY KEY(name))"; + } + + @Override + public final TestForeignKeyDatabase.SimpleModel loadFromCursor(FlowCursor cursor, + DatabaseWrapper wrapper) { + TestForeignKeyDatabase.SimpleModel model = new TestForeignKeyDatabase.SimpleModel(""); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TestForeignKeyDatabase.SimpleModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/TestDatabase.java b/entry/src/ohosTest/java/com/dbflow5/TestDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..d60b2118e8af0d24bf603f1e0398de1a3e00c5b7 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/TestDatabase.java @@ -0,0 +1,38 @@ +package com.dbflow5; + +import com.dbflow5.annotation.Database; +import com.dbflow5.annotation.Migration; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.migration.BaseMigration; +import com.dbflow5.migration.UpdateTableMigration; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; + +/** + * TestDatabase + * + * @author wangyin + * @since 2021/06/19 + */ +@Database(version = 1) +public abstract class TestDatabase extends DBFlowDatabase { + @Migration(version = 1, database = TestDatabase.class, priority = 5) + public static class TestMigration extends UpdateTableMigration { + public void onPreMigrate() { + super.onPreMigrate(); + set(SimpleModel_Table.name.eq("Test")).where(SimpleModel_Table.name.eq("Test1")); + } + + public TestMigration() { + super(SimpleTestModels.SimpleModel.class); + } + + } + @Migration(version = 1, database = TestDatabase.class, priority = 1) + public static class SecondMigration extends BaseMigration { + public void migrate(DatabaseWrapper database) { + + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/TestExtensions.java b/entry/src/ohosTest/java/com/dbflow5/TestExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..9ec7a7923aba71069b7b7ab0e1f82ba046769271 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/TestExtensions.java @@ -0,0 +1,39 @@ +package com.dbflow5; + +import com.dbflow5.sql.Query; + +import org.junit.Assert; + +import java.util.function.Function; + +import static org.junit.Assert.fail; + +/** + * TestExtensions + * + * @author wangyin + * @since 2021/06/19 + */ +public class TestExtensions { + public static void assertEquals(String string, Query query) { + Assert.assertEquals(string, query.getQuery().trim()); + } + + public static void assertEquals(Query query, Query actual) { + Assert.assertEquals(query, actual.getQuery().trim()); + } + + public static void assertThrowsException(Class expectedException, Function function) { + try { + if (function != null) { + function.apply(null); + fail("Expected call to fail. Unexpectedly passed"); + } + } catch (Exception e) { + if (e.getClass() != expectedException) { + e.printStackTrace(); + fail("Expected "+expectedException+" but got " + e.getClass()); + } + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabase.java b/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..54062a7367b3d740a8cc55f457baa4ef0de07d7f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabase.java @@ -0,0 +1,57 @@ +package com.dbflow5; + +import com.dbflow5.annotation.Database; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.config.DBFlowDatabase; + +@Database(version = 1, foreignKeyConstraintsEnforced = true) +public abstract class TestForeignKeyDatabase extends DBFlowDatabase { + @Table(database = TestForeignKeyDatabase.class) + public static class SimpleModel{ + @PrimaryKey + public String name = ""; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SimpleModel(String name) { + this.name = name; + } + } + + @Table(database = TestForeignKeyDatabase.class) + public static class SimpleForeignModel{ + @PrimaryKey + public int id; + @ForeignKey + public SimpleModel model; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public SimpleModel getModel() { + return model; + } + + public void setModel(SimpleModel model) { + this.model = model; + } + + public SimpleForeignModel(int id, SimpleModel model) { + this.id = id; + this.model = model; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabaseTest.java b/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabaseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f19e0c5e776505db34ce9fb973a7ead6e9f7a1f4 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/TestForeignKeyDatabaseTest.java @@ -0,0 +1,24 @@ +package com.dbflow5; + +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import org.junit.Test; + +/** + * TestForeignKeyDatabaseTest + * + * @author wangyin + * @since 2021/06/19 + */ +public class TestForeignKeyDatabaseTest extends BaseUnitTest { + @Test + public void verifyDB() { + TestForeignKeyDatabase db = FlowManager.getDatabase(TestForeignKeyDatabase.class); + try (DatabaseStatement statement = db.compileStatement("PRAGMA foreign_keys;")) { + long enabled = statement.simpleQueryForLong(); + assert (enabled == 1L); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/User.java b/entry/src/ohosTest/java/com/dbflow5/User.java new file mode 100644 index 0000000000000000000000000000000000000000..11bea5a8a27b4abe6935e1f373aa99c89e653a85 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/User.java @@ -0,0 +1,63 @@ +package com.dbflow5; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.contentobserver.ContentObserverDatabase; + +/** + * User + * + * @author wangyin + * @since 2021/06/19 + */ +@Table(database = ContentObserverDatabase.class, name = "User2") +public class User { + @PrimaryKey + public int id; + @Column + public String firstName; + @Column + public String lastName; + @Column + public String email; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public User(int id, String firstName, String lastName, String email) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/User_Table.java b/entry/src/ohosTest/java/com/dbflow5/User_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..d4c177e45b7d7021aa6e2551ad9647cb0f675689 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/User_Table.java @@ -0,0 +1,140 @@ +package com.dbflow5; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class User_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(User.class, "id"); + + public static final Property firstName = new Property(User.class, "firstName"); + + public static final Property lastName = new Property(User.class, "lastName"); + + public static final Property email = new Property(User.class, "email"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,firstName,lastName,email}; + + public User_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return User.class; + } + + @Override + public final String getName() { + return "User2"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "firstName": { + return firstName; + } + case "lastName": { + return lastName; + } + case "email": { + return email; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getFirstName()); + statement.bindStringOrNull(3, model.getLastName()); + statement.bindStringOrNull(4, model.getEmail()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getFirstName()); + statement.bindStringOrNull(3, model.getLastName()); + statement.bindStringOrNull(4, model.getEmail()); + statement.bindLong(5, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO User2(id,firstName,lastName,email) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO User2(id,firstName,lastName,email) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE User2 SET id=?,firstName=?,lastName=?,email=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM User2 WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS User2(id INTEGER, firstName TEXT, lastName TEXT, email TEXT, PRIMARY KEY(id))"; + } + + @Override + public final User loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + User model = new User(0, null, null, null); + model.setId(cursor.getIntOrDefault("id")); + model.setFirstName(cursor.getStringOrDefault("firstName")); + model.setLastName(cursor.getStringOrDefault("lastName")); + model.setEmail(cursor.getStringOrDefault("email")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(User model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/CipherDatabaseCipherDatabase_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/CipherDatabaseCipherDatabase_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..43462b412ba53a12e8022421ecc59f9e15f6b258 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/CipherDatabaseCipherDatabase_Database.java @@ -0,0 +1,41 @@ +package com.dbflow5.config; + +import com.dbflow5.sqlcipher.CipherDatabase; +import com.dbflow5.sqlcipher.CipherModel_Table; +import java.lang.Class; +import java.lang.Override; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class CipherDatabaseCipherDatabase_Database extends CipherDatabase { + public CipherDatabaseCipherDatabase_Database(DatabaseHolder holder) { + addModelAdapter(new CipherModel_Table(this), holder); + } + + @Override + public final Class associatedDatabaseClassFile() { + return CipherDatabase.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/ConfigIntegrationTest.java b/entry/src/ohosTest/java/com/dbflow5/config/ConfigIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..113fa4d11873eb4af2170f62801677ca2d456a4b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/ConfigIntegrationTest.java @@ -0,0 +1,72 @@ +package com.dbflow5.config; + +import com.dbflow5.TestDatabase; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.ModelSaver; +import com.dbflow5.BaseUnitTest; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.models.SimpleTestModels; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class ConfigIntegrationTest extends BaseUnitTest { + + @Before + public void setup() { + FlowManager.reset(); + FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); + } + + @Test + public void test_flowConfig() { + FlowConfig config = FlowConfig.flowConfig(getContext(), builder -> { + builder.openDatabasesOnInit(true); + return null; + }); + Assert.assertTrue(config.openDatabasesOnInit); + Assert.assertTrue(config.databaseConfigMap.isEmpty()); + Assert.assertTrue(config.databaseHolders.isEmpty()); + } + + @Test + public void test_tableConfig() { + ListModelLoader customListModelLoader = new ListModelLoader<>(SimpleTestModels.SimpleModel.class); + SingleModelLoader singleModelLoader = new SingleModelLoader<>(SimpleTestModels.SimpleModel.class); + ModelSaver modelSaver = new ModelSaver<>(); + + FlowManager.init(FlowConfig.flowConfig(getContext(), builder -> { + builder.database(TestDatabase.class, builder1 -> { + builder1.table(SimpleTestModels.SimpleModel.class, simpleModelBuilder -> { + simpleModelBuilder.singleModelLoader(singleModelLoader); + simpleModelBuilder.listModelLoader(customListModelLoader); + simpleModelBuilder.modelAdapterModelSaver(modelSaver); + return null; + }); + return null; + }, OhosSQLiteOpenHelper.createHelperCreator(getContext())); + return null; + })); + + FlowConfig flowConfig = FlowManager.getConfig(); + Assert.assertNotNull(flowConfig); + + DatabaseConfig databaseConfig = flowConfig.databaseConfigMap.get(TestDatabase.class); + Assert.assertNotNull(databaseConfig); + + TableConfig config = databaseConfig.tableConfigMap.get(SimpleTestModels.SimpleModel.class); + Assert.assertNotNull(config); + + Assert.assertEquals(config.listModelLoader, customListModelLoader); + Assert.assertEquals(config.singleModelLoader, singleModelLoader); + + ModelAdapter modelAdapter = FlowManager.modelAdapter(SimpleTestModels.SimpleModel.class); + Assert.assertEquals(modelAdapter.getListModelLoader(), customListModelLoader); + Assert.assertEquals(modelAdapter.getSingleModelLoader(), singleModelLoader); + Assert.assertEquals(modelAdapter.getModelSaver(), modelSaver); + } + +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/ContentDatabaseContentDatabase_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/ContentDatabaseContentDatabase_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..48aa14094069ddbd07063010f6295421ffbcc756 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/ContentDatabaseContentDatabase_Database.java @@ -0,0 +1,45 @@ +package com.dbflow5.config; + +import com.dbflow5.provider.ContentProviderModel_Table; +import com.dbflow5.provider.ContentProviderObjects; +import com.dbflow5.provider.NoteModel_Table; +import com.dbflow5.provider.TestSyncableModel_Table; +import java.lang.Class; +import java.lang.Override; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class ContentDatabaseContentDatabase_Database extends ContentProviderObjects.ContentDatabase { + public ContentDatabaseContentDatabase_Database(DatabaseHolder holder) { + addModelAdapter(new ContentProviderModel_Table(this), holder); + addModelAdapter(new NoteModel_Table(this), holder); + addModelAdapter(new TestSyncableModel_Table(this), holder); + } + + @Override + public final Class associatedDatabaseClassFile() { + return ContentProviderObjects.ContentDatabase.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/ContentObserverDatabaseContentObserverDatabase_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/ContentObserverDatabaseContentObserverDatabase_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..9795af5f282f9c81870c6d4a08387f35d59d139d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/ContentObserverDatabaseContentObserverDatabase_Database.java @@ -0,0 +1,38 @@ +package com.dbflow5.config; + +import com.dbflow5.contentobserver.ContentObserverDatabase; +import com.dbflow5.contentobserver.User_Table; +import java.lang.Class; +import java.lang.Override; + +public final class ContentObserverDatabaseContentObserverDatabase_Database extends ContentObserverDatabase { + public ContentObserverDatabaseContentObserverDatabase_Database(DatabaseHolder holder) { + addModelAdapter(new User_Table(this), holder); + addModelAdapter(new User_Table(this), holder); + } + + @Override + public final Class associatedDatabaseClassFile() { + return ContentObserverDatabase.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/DatabaseConfigTest.java b/entry/src/ohosTest/java/com/dbflow5/config/DatabaseConfigTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b3dc8db71da1b5241bdfcfea47617193a7adbf6a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/DatabaseConfigTest.java @@ -0,0 +1,107 @@ +package com.dbflow5.config; +import com.dbflow5.DemoApp; +import com.dbflow5.TestDatabase; +import com.dbflow5.database.*; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.transaction.BaseTransactionManager; +import com.dbflow5.transaction.ITransactionQueue; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + + +public class DatabaseConfigTest extends BaseUnitTest { + + private FlowConfig.Builder builder; + + @Before + public void setup() { + FlowManager.reset(); + FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); + builder = new FlowConfig.Builder(getContext()); + } + + @Test + public void test_databaseConfig() { + DatabaseCallback helperListener = new DatabaseCallback(){ + + @Override + public void onOpen(DatabaseWrapper database) { + } + + @Override + public void onCreate(DatabaseWrapper database) { + } + + @Override + public void onUpgrade(DatabaseWrapper database, int oldVersion, int newVersion) { + } + + @Override + public void onDowngrade(DatabaseWrapper databaseWrapper, int oldVersion, int newVersion) { + } + + @Override + public void onConfigure(DatabaseWrapper db) { + } + }; + OpenHelper customOpenHelper = Mockito.mock(OpenHelper.class, Mockito.withSettings()); + + DatabaseConfig.OpenHelperCreator openHelperCreator = (db, callback) -> customOpenHelper; + + AtomicReference testTransactionManager = null; + DatabaseConfig.TransactionManagerCreator managerCreator = db -> { + testTransactionManager.set(new TestTransactionManager(db)); + return testTransactionManager.get(); + }; + + FlowManager.init(builder.database(TestDatabase.class, builder -> { + builder.databaseName("Test"); + builder.helperListener(helperListener); + builder.transactionManagerCreator(managerCreator); + return null; + }, openHelperCreator).build()); + + FlowConfig flowConfig = FlowManager.getConfig(); + Assert.assertNotNull(flowConfig); + + DatabaseConfig databaseConfig = flowConfig.databaseConfigMap.get(TestDatabase.class); + Assert.assertEquals("Test", databaseConfig.databaseName); + Assert.assertEquals(".db", databaseConfig.databaseExtensionName); + Assert.assertEquals(databaseConfig.transactionManagerCreator, managerCreator); + Assert.assertEquals(databaseConfig.databaseClass, TestDatabase.class); + Assert.assertEquals(databaseConfig.openHelperCreator, openHelperCreator); + Assert.assertEquals(databaseConfig.callback, helperListener); + Assert.assertTrue(databaseConfig.tableConfigMap.isEmpty()); + + TestDatabase databaseDefinition = FlowManager.database(TestDatabase.class, testDatabase -> null); + Assert.assertEquals(databaseDefinition.transactionManager, testTransactionManager != null? testTransactionManager.get() : null); + Assert.assertEquals(databaseDefinition.openHelper(), customOpenHelper); + } + + @Test + public void test_EmptyName() { + FlowManager.init(builder.database(TestDatabase.class, builder1 -> { + builder1.databaseName("Test"); + builder1.extensionName(""); + return null; + }, OhosSQLiteOpenHelper.createHelperCreator(getContext())).build()); + + DatabaseConfig databaseConfig=FlowManager.getConfig().getConfigForDatabase(DBFlowDatabase.class); + if(null!=databaseConfig){ + Assert.assertEquals("Test", databaseConfig.databaseName); + Assert.assertEquals("", databaseConfig.databaseExtensionName); + } + } + + static class TestTransactionManager extends BaseTransactionManager { + public TestTransactionManager(DBFlowDatabase databaseDefinition){ + super(Mockito.mock(ITransactionQueue.class, Mockito.withSettings()), databaseDefinition); + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/GeneratedDatabaseHolder.java b/entry/src/ohosTest/java/com/dbflow5/config/GeneratedDatabaseHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..12506ae2d91eeac9e658108f00f8408b62ede746 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/GeneratedDatabaseHolder.java @@ -0,0 +1,53 @@ +package com.dbflow5.config; + +import com.dbflow5.converter.TypeConverters.BigDecimalConverter; +import com.dbflow5.converter.TypeConverters.BigIntegerConverter; +import com.dbflow5.converter.TypeConverters.BooleanConverter; +import com.dbflow5.converter.TypeConverters.CalendarConverter; +import com.dbflow5.converter.TypeConverters.CharConverter; +import com.dbflow5.converter.TypeConverters.DateConverter; +import com.dbflow5.converter.TypeConverters.SqlDateConverter; +import com.dbflow5.converter.TypeConverters.UUIDConverter; +import com.dbflow5.models.ForeignKeyModels; +import com.dbflow5.models.ProspectQuizs; +import com.dbflow5.models.QueryModels; +import com.dbflow5.models.SimpleTestModels; + +import java.lang.Boolean; +import java.lang.Character; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Set; +import java.util.UUID; + +public final class GeneratedDatabaseHolder extends DatabaseHolder { + public GeneratedDatabaseHolder() { + typeConverters.put(byte[].class, new SimpleTestModels.BlobConverter()); + typeConverters.put(QueryModels.CustomBlobModel.MyBlob.class, new QueryModels.CustomBlobModel.MyTypeConverter()); + typeConverters.put(ForeignKeyModels.DoubleToDouble.class, new ForeignKeyModels.DoubleConverter()); + typeConverters.put(Boolean.class, new BooleanConverter()); + typeConverters.put(Character.class, new CharConverter()); + typeConverters.put(BigDecimal.class, new BigDecimalConverter()); + typeConverters.put(BigInteger.class, new BigIntegerConverter()); + typeConverters.put(Date.class, new SqlDateConverter()); + typeConverters.put(Time.class, new SqlDateConverter()); + typeConverters.put(Timestamp.class, new SqlDateConverter()); + typeConverters.put(Calendar.class, new CalendarConverter()); + typeConverters.put(GregorianCalendar.class, new CalendarConverter()); + typeConverters.put(java.util.Date.class, new DateConverter()); + typeConverters.put(Set.class, new ProspectQuizs.MutableSetTypeConverter()); + typeConverters.put(UUID.class, new UUIDConverter()); + new CipherDatabaseCipherDatabase_Database(this); + new ContentDatabaseContentDatabase_Database(this); + new ContentObserverDatabaseContentObserverDatabase_Database(this); + new MigratedPrepackagedDBMigratedPrepackagedDB_Database(this); + new PrepackagedDBPrepackagedDB_Database(this); + new TestDatabaseTestDatabase_Database(this); + new TestForeignKeyDatabaseTestForeignKeyDatabase_Database(this); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/MigratedPrepackagedDBMigratedPrepackagedDB_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/MigratedPrepackagedDBMigratedPrepackagedDB_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..32ab066b00a6d3e0b8749b90d1eb0d1fe1ef958c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/MigratedPrepackagedDBMigratedPrepackagedDB_Database.java @@ -0,0 +1,44 @@ +package com.dbflow5.config; + +import com.dbflow5.prepackaged.Dog2_Table; +import com.dbflow5.prepackaged.PrepackagedDB; + +import java.lang.Class; +import java.lang.Override; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class MigratedPrepackagedDBMigratedPrepackagedDB_Database extends PrepackagedDB.MigratedPrepackagedDB { + public MigratedPrepackagedDBMigratedPrepackagedDB_Database(DatabaseHolder holder) { + addModelAdapter(new Dog2_Table(this), holder); + addMigration(2, new PrepackagedDB.MigratedPrepackagedDB.AddNewFieldMigration()); + addMigration(2, new PrepackagedDB.MigratedPrepackagedDB.AddSomeDataMigration()); + } + + @Override + public final Class associatedDatabaseClassFile() { + return PrepackagedDB.MigratedPrepackagedDB.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 2; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/PrepackagedDBPrepackagedDB_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/PrepackagedDBPrepackagedDB_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..bd0d8925f3b04837c7ddb2c5a9dff887c2d9db31 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/PrepackagedDBPrepackagedDB_Database.java @@ -0,0 +1,41 @@ +package com.dbflow5.config; + +import com.dbflow5.prepackaged.Dog_Table; +import com.dbflow5.prepackaged.PrepackagedDB; +import java.lang.Class; +import java.lang.Override; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class PrepackagedDBPrepackagedDB_Database extends PrepackagedDB { + public PrepackagedDBPrepackagedDB_Database(DatabaseHolder holder) { + addModelAdapter(new Dog_Table(this), holder); + } + + @Override + public final Class associatedDatabaseClassFile() { + return PrepackagedDB.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/TestDatabaseTestDatabase_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/TestDatabaseTestDatabase_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..6fd46220838a6b5f1c1c4fdfe7cc0a3086d6635c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/TestDatabaseTestDatabase_Database.java @@ -0,0 +1,186 @@ +package com.dbflow5.config; + +import com.dbflow5.TestDatabase; +import com.dbflow5.livedata.LiveDataModel_Table; +import com.dbflow5.migration.MigrationModels; +import com.dbflow5.models.Account_Table; +import com.dbflow5.models.AllFieldsModel_Table; +import com.dbflow5.models.AllFieldsQueryModel_QueryTable; +import com.dbflow5.models.Artist_Song_Table; +import com.dbflow5.models.Artist_Table; +import com.dbflow5.models.AuthorNameQuery_QueryTable; +import com.dbflow5.models.AuthorName_QueryTable; +import com.dbflow5.models.AuthorView_ViewTable; +import com.dbflow5.models.Author_Table; +import com.dbflow5.models.AutoIncrementingModel_Table; +import com.dbflow5.models.BlogDeferred_Table; +import com.dbflow5.models.BlogPrimary_Table; +import com.dbflow5.models.BlogRefNoModel_Table; +import com.dbflow5.models.BlogRef_Table; +import com.dbflow5.models.BlogStubbed_Table; +import com.dbflow5.models.Blog_Table; +import com.dbflow5.models.CharModel_Table; +import com.dbflow5.models.Coordinate_Table; +import com.dbflow5.models.Currency_Table; +import com.dbflow5.models.CustomBlobModel_QueryTable; +import com.dbflow5.models.DefaultModel_Table; +import com.dbflow5.models.Dog_Table; +import com.dbflow5.models.DontAssignDefaultModel_Table; +import com.dbflow5.models.DontCreateModel_Table; +import com.dbflow5.models.EnumModel_Table; +import com.dbflow5.models.EnumTypeConverterModel_Table; +import com.dbflow5.models.FeedEntry_Table; +import com.dbflow5.models.Fts3Model_Table; +import com.dbflow5.models.Fts4Model_Table; +import com.dbflow5.models.Fts4VirtualModel2_Table; +import com.dbflow5.models.IndexModel_Table; +import com.dbflow5.models.Inner_Table; +import com.dbflow5.models.InternalClass_Table; +import com.dbflow5.models.Location2_QueryTable; +import com.dbflow5.models.Location_QueryTable; +import com.dbflow5.models.NonNullKotlinModel_Table; +import com.dbflow5.models.NonTypical.nonTypicalClassName_Table; +import com.dbflow5.models.NotNullReferenceModel_Table; +import com.dbflow5.models.NullableNumbers_Table; +import com.dbflow5.models.NumberModel_Table; +import com.dbflow5.models.OneToManyBaseModel_Table; +import com.dbflow5.models.OneToManyModel_Table; +import com.dbflow5.models.OrderCursorModel_Table; +import com.dbflow5.models.Owner_Table; +import com.dbflow5.models.Path_Table; +import com.dbflow5.models.Position2_Table; +import com.dbflow5.models.PositionWithTypeConverter_Table; +import com.dbflow5.models.Position_Table; +import com.dbflow5.models.PriorityView_ViewTable; +import com.dbflow5.models.ProspectQuizEntry_Table; +import com.dbflow5.models.ProspectQuiz_Table; +import com.dbflow5.models.Refund_Table; +import com.dbflow5.models.SimpleCacheObject_Table; +import com.dbflow5.models.SimpleCustomModel_QueryTable; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleQuickCheckModel_Table; +import com.dbflow5.models.Song_Table; +import com.dbflow5.models.SqlListenerModel_Table; +import com.dbflow5.models.SubclassAllFields_Table; +import com.dbflow5.models.TempModel_Table; +import com.dbflow5.models.TestModelChild_Table; +import com.dbflow5.models.TestModelParent_Table; +import com.dbflow5.models.Transfer2_Table; +import com.dbflow5.models.Transfer_Table; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.models.TypeConverterModel_Table; +import com.dbflow5.models.UniqueModel_Table; +import com.dbflow5.models.UserInfo_Table; +import com.dbflow5.models.java.JavaModelView_ViewTable; +import com.dbflow5.models.java.JavaModel_Table; +import com.dbflow5.models.java.otherpackage.ExampleModel_Table; +import com.dbflow5.rx2.query.SimpleRXModel_Table; +import java.lang.Class; +import java.lang.Override; + +public final class TestDatabaseTestDatabase_Database extends TestDatabase { + public TestDatabaseTestDatabase_Database(DatabaseHolder holder) { + addModelAdapter(new Account_Table(holder, this), holder); + addModelAdapter(new AllFieldsModel_Table(this), holder); + addModelAdapter(new Artist_Song_Table(this), holder); + addModelAdapter(new Artist_Table(this), holder); + addModelAdapter(new Author_Table(this), holder); + addModelAdapter(new AutoIncrementingModel_Table(this), holder); + addModelAdapter(new BlogDeferred_Table(this), holder); + addModelAdapter(new BlogPrimary_Table(this), holder); + addModelAdapter(new BlogRefNoModel_Table(this), holder); + addModelAdapter(new BlogRef_Table(this), holder); + addModelAdapter(new BlogStubbed_Table(this), holder); + addModelAdapter(new Blog_Table(this), holder); + addModelAdapter(new CharModel_Table(holder, this), holder); + addModelAdapter(new Coordinate_Table(this), holder); + addModelAdapter(new Currency_Table(this), holder); + addModelAdapter(new DefaultModel_Table(this), holder); + addModelAdapter(new Dog_Table(this), holder); + addModelAdapter(new DontAssignDefaultModel_Table(holder, this), holder); + addModelAdapter(new DontCreateModel_Table(this), holder); + addModelAdapter(new EnumModel_Table(this), holder); + addModelAdapter(new EnumTypeConverterModel_Table(this), holder); + addModelAdapter(new ExampleModel_Table(this), holder); + addModelAdapter(new FeedEntry_Table(this), holder); + addModelAdapter(new Fts3Model_Table(this), holder); + addModelAdapter(new Fts4Model_Table(this), holder); + addModelAdapter(new Fts4VirtualModel2_Table(this), holder); + addModelAdapter(new IndexModel_Table(holder, this), holder); + addModelAdapter(new Inner_Table(this), holder); + addModelAdapter(new InternalClass_Table(this), holder); + addModelAdapter(new JavaModel_Table(this), holder); + addModelAdapter(new LiveDataModel_Table(this), holder); + addModelAdapter(new NonNullKotlinModel_Table(holder, this), holder); + addModelAdapter(new NotNullReferenceModel_Table(this), holder); + addModelAdapter(new NullableNumbers_Table(holder, this), holder); + addModelAdapter(new NumberModel_Table(this), holder); + addModelAdapter(new OneToManyBaseModel_Table(this), holder); + addModelAdapter(new OneToManyModel_Table(this), holder); + addModelAdapter(new OrderCursorModel_Table(this), holder); + addModelAdapter(new Owner_Table(this), holder); + addModelAdapter(new Path_Table(this), holder); + addModelAdapter(new Position2_Table(holder, this), holder); + addModelAdapter(new PositionWithTypeConverter_Table(this), holder); + addModelAdapter(new Position_Table(holder, this), holder); + addModelAdapter(new ProspectQuizEntry_Table(this), holder); + addModelAdapter(new ProspectQuiz_Table(this), holder); + addModelAdapter(new Refund_Table(holder, this), holder); + addModelAdapter(new SimpleCacheObject_Table(this), holder); + addModelAdapter(new SimpleModel_Table(this), holder); + addModelAdapter(new SimpleQuickCheckModel_Table(this), holder); + addModelAdapter(new SimpleRXModel_Table(this), holder); + addModelAdapter(new Song_Table(this), holder); + addModelAdapter(new SqlListenerModel_Table(this), holder); + addModelAdapter(new SubclassAllFields_Table(this), holder); + addModelAdapter(new TempModel_Table(this), holder); + addModelAdapter(new TestModelChild_Table(this), holder); + addModelAdapter(new TestModelParent_Table(this), holder); + addModelAdapter(new Transfer2_Table(holder, this), holder); + addModelAdapter(new Transfer_Table(holder, this), holder); + addModelAdapter(new TwoColumnModel_Table(this), holder); + addModelAdapter(new TypeConverterModel_Table(this), holder); + addModelAdapter(new UniqueModel_Table(this), holder); + addModelAdapter(new UserInfo_Table(this), holder); + addModelAdapter(new nonTypicalClassName_Table(this), holder); + addModelViewAdapter(new PriorityView_ViewTable(this), holder); + addModelViewAdapter(new AuthorView_ViewTable(this), holder); + addModelViewAdapter(new JavaModelView_ViewTable(this), holder); + addRetrievalAdapter(new AllFieldsQueryModel_QueryTable(this), holder); + addRetrievalAdapter(new AuthorNameQuery_QueryTable(this), holder); + addRetrievalAdapter(new AuthorName_QueryTable(this), holder); + addRetrievalAdapter(new CustomBlobModel_QueryTable(holder, this), holder); + addRetrievalAdapter(new Location2_QueryTable(holder, this), holder); + addRetrievalAdapter(new Location_QueryTable(holder, this), holder); + addRetrievalAdapter(new SimpleCustomModel_QueryTable(this), holder); + addMigration(1, new MigrationModels.FirstMigration()); + addMigration(1, new SecondMigration()); + addMigration(1, new SecondMigration()); + addMigration(1, new TestMigration()); + } + + @Override + public final Class associatedDatabaseClassFile() { + return TestDatabase.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return false; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/config/TestForeignKeyDatabaseTestForeignKeyDatabase_Database.java b/entry/src/ohosTest/java/com/dbflow5/config/TestForeignKeyDatabaseTestForeignKeyDatabase_Database.java new file mode 100644 index 0000000000000000000000000000000000000000..887eb2124f851eba99f6fce599ea6a7562bf2034 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/config/TestForeignKeyDatabaseTestForeignKeyDatabase_Database.java @@ -0,0 +1,43 @@ +package com.dbflow5.config; + +import com.dbflow5.SimpleForeignModel_Table; +import com.dbflow5.SimpleModel_Table; +import com.dbflow5.TestForeignKeyDatabase; +import java.lang.Class; +import java.lang.Override; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class TestForeignKeyDatabaseTestForeignKeyDatabase_Database extends TestForeignKeyDatabase { + public TestForeignKeyDatabaseTestForeignKeyDatabase_Database(DatabaseHolder holder) { + addModelAdapter(new SimpleForeignModel_Table(this), holder); + addModelAdapter(new SimpleModel_Table(this), holder); + } + + @Override + public final Class associatedDatabaseClassFile() { + return TestForeignKeyDatabase.class; + } + + @Override + public final boolean isForeignKeysSupported() { + return true; + } + + @Override + public final boolean backupEnabled() { + return false; + } + + @Override + public final boolean areConsistencyChecksEnabled() { + return false; + } + + @Override + public final int databaseVersion() { + return 1; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.java b/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..9bbfc351d5e7ceb54ac3fef58c473ade9d1ba51b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.java @@ -0,0 +1,12 @@ +package com.dbflow5.contentobserver; + +import com.dbflow5.annotation.Database; +import com.dbflow5.config.DBFlowDatabase; + + +@Database(version = 1) +public abstract class ContentObserverDatabase extends DBFlowDatabase { + public ContentObserverDatabase() { + super(); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverTest.java b/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cae1746cfd51b6a66cbc5becdaeda1de87c73d7b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/contentobserver/ContentObserverTest.java @@ -0,0 +1,124 @@ +package com.dbflow5.contentobserver; + +import com.dbflow5.*; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLOperator; +import com.dbflow5.query.SQLite; +import com.dbflow5.runtime.ContentResolverNotifier; +import com.dbflow5.runtime.FlowContentObserver; +import com.dbflow5.structure.ChangeAction; +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import ohos.utils.net.Uri; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.function.BiFunction; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; + +public class ContentObserverTest extends BaseUnitTest { + @Rule + public DBFlowInstrumentedTestRule dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> { + builder.database(ContentObserverDatabase.class, builder1 -> { + builder1.modelNotifier(new ContentResolverNotifier(AbilityDelegatorRegistry.getAbilityDelegator().getAppContext(), "com.grosner.content")); + builder1.transactionManagerCreator(ImmediateTransactionManager::new); + return null; + }, OhosSQLiteOpenHelper.createHelperCreator(AbilityDelegatorRegistry.getAbilityDelegator().getAppContext())); + return null; + }); + + public String contentUri = "com.grosner.content"; + + private User user; + + @Before + public void setupUser() { + FlowManager.database(ContentObserverDatabase.class, dbFlowDatabase -> { + SQLite.delete(User.class).execute(dbFlowDatabase); + return null; + }); + user = new User(5, "Something", 55); + } + + @Test + public void testSpecificUris() { + OperatorGroup conditionGroup = FlowManager.modelAdapter(User.class).getPrimaryConditionClause(user); + Uri uri = SqlUtils.getNotificationUri(contentUri, + User.class, ChangeAction.DELETE, + conditionGroup.conditions()); + + assertEquals(uri.getDecodedAuthority(), contentUri); + assertEquals(FlowManager.tableName(User.class), uri.getFirstQueryParamByKey(SqlUtils.TABLE_QUERY_PARAM)); + assertEquals(uri.getDecodedFragment(), ChangeAction.DELETE.name()); + assertEquals(Uri.decode(uri.getFirstQueryParamByKey(Uri.encode(User_Table.id.getQuery()))), "5"); + assertEquals(Uri.decode(uri.getFirstQueryParamByKey(Uri.encode(User_Table.name.getQuery()))), "Something"); + } + + @Test + public void testSpecificUrlInsert() { + //assertProperConditions(ChangeAction.INSERT) { user, db -> user.insert(db) } + } + + @Test + public void testSpecificUrlUpdate() { + // assertProperConditions(ChangeAction.UPDATE) { user, db -> user.apply { age = 56 }.update(db) } + + } + + @Test + public void testSpecificUrlSave() { + // insert on SAVE + //assertProperConditions(ChangeAction.INSERT) { user, db -> user.apply { age = 57 }.save(db) } + } + + @Test + public void testSpecificUrlDelete() { + // user.save(databaseForTable()) + // assertProperConditions(ChangeAction.DELETE) { user, db -> user.delete(db) } + } + + private void assertProperConditions(ChangeAction action, BiFunction userFunc) throws InterruptedException { + FlowContentObserver contentObserver = new FlowContentObserver(contentUri, null); + CountDownLatch countDownLatch = new CountDownLatch(1); + MockOnModelStateChangedListener mockOnModelStateChangedListener = new MockOnModelStateChangedListener(countDownLatch); + contentObserver.addModelChangeListener(mockOnModelStateChangedListener); + contentObserver.registerForContentChanges(DemoApp.context, User.class); + + userFunc.apply(user, FlowManager.databaseForTable(User.class, dbFlowDatabase -> null)); + countDownLatch.await(); + + SQLOperator[] ops = mockOnModelStateChangedListener.operators; + assertEquals(2, ops.length); + assertEquals(ops[0].columnName(), User_Table.id.getQuery()); + assertEquals(ops[1].columnName(), User_Table.name.getQuery()); + assertEquals(ops[1].value(), "Something"); + assertEquals(ops[0].value(), "5"); + assertEquals(action, mockOnModelStateChangedListener.action); + + contentObserver.removeModelChangeListener(mockOnModelStateChangedListener); + contentObserver.unregisterForContentChanges(DemoApp.context); + } + + static class MockOnModelStateChangedListener implements FlowContentObserver.OnModelStateChangedListener { + public CountDownLatch countDownLatch; + public ChangeAction action = null; + public SQLOperator[] operators = null; + + public MockOnModelStateChangedListener(CountDownLatch countDownLatch) { + this.countDownLatch = countDownLatch; + } + + @Override + public void onModelStateChanged(Class table, ChangeAction action, SQLOperator[] primaryKeyValues) { + this.action = action; + operators = primaryKeyValues; + countDownLatch.countDown(); + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/contentobserver/User.java b/entry/src/ohosTest/java/com/dbflow5/contentobserver/User.java new file mode 100644 index 0000000000000000000000000000000000000000..f971076639340ee7a8540912b6b604d3981cc7f3 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/contentobserver/User.java @@ -0,0 +1,44 @@ +package com.dbflow5.contentobserver; + +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +@Table(database = ContentObserverDatabase.class) +public class User { + @PrimaryKey + public int id; + @PrimaryKey + public String name; + @PrimaryKey + public int age; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public User(int id, String name, int age) { + this.id = id; + this.name = name; + this.age = age; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/contentobserver/User_Table.java b/entry/src/ohosTest/java/com/dbflow5/contentobserver/User_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..5b250d3baae1572dd95ea5f25c7efafce1fdff15 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/contentobserver/User_Table.java @@ -0,0 +1,154 @@ +package com.dbflow5.contentobserver; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class User_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(User.class, "id"); + + /** + * Primary Key */ + public static final Property name = new Property(User.class, "name"); + + public static final Property age = new Property(User.class, "age"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,age}; + + public User_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return User.class; + } + + @Override + public final String getName() { + return "User"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "age": { + return age; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getAge()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getAge()); + statement.bindLong(4, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(5, model.getName()); + } else { + statement.bindString(5, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, User model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO User(id,name,age) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO User(id,name,age) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE User SET id=?,name=?,age=? WHERE id=? AND name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM User WHERE id=? AND name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS User(id INTEGER, name TEXT, age INTEGER, PRIMARY KEY(id, name))"; + } + + @Override + public final User loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + User model = new User(0, "", 0); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + model.setAge(cursor.getIntOrDefault("age")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(User model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel.java b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..37c4fd31f0ba9b93cfdc29b3028012d754eeda56 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel.java @@ -0,0 +1,34 @@ +package com.dbflow5.livedata; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +@Table(database = TestDatabase.class) +public class LiveDataModel { + @PrimaryKey + public String id; + @PrimaryKey + public int name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getName() { + return name; + } + + public void setName(int name) { + this.name = name; + } + + public LiveDataModel(String id, int name) { + this.id=id; + this.name=name; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..718eccf8c3ca2e99dce14a6d2784212b4cb71c67 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataModel_Table.java @@ -0,0 +1,142 @@ +package com.dbflow5.livedata; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +public final class LiveDataModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(LiveDataModel.class, "id"); + + public static final Property name = new Property(LiveDataModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public LiveDataModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return LiveDataModel.class; + } + + @Override + public final String getName() { + return "LiveDataModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "`id`": { + return id; + } + case "`name`": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, LiveDataModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + statement.bindLong(2, (long)model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, LiveDataModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + statement.bindLong(2, (long)model.getName()); + if (model.getId() != null) { + statement.bindString(3, model.getId()); + } else { + statement.bindString(3, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, LiveDataModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO LiveDataModel(id,name) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO LiveDataModel(id,name) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE LiveDataModel SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM LiveDataModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS LiveDataModel(id TEXT, name INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final LiveDataModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + LiveDataModel model = new LiveDataModel("", 0); + model.setId(cursor.getStringOrDefault("id", "")); + model.setName(cursor.getIntOrDefault("name")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(LiveDataModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataTest.java b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f3d7fe11fcb653a3152437da69e6856c9a838bb4 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/livedata/LiveDataTest.java @@ -0,0 +1,64 @@ +package com.dbflow5.livedata; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.DemoApp; +import com.dbflow5.ImmediateTransactionManager; +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.query.SQLite; +import com.dbflow5.structure.Model; +import ohos.aafwk.ability.Ability; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.List; +import java.util.function.Function; + +public class LiveDataTest extends BaseUnitTest { + + @Before + public void setup() { + FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); + FlowManager.init(DemoApp.context, builder -> { + builder.database(TestDatabase.class, builder1 -> { + builder1.transactionManagerCreator(ImmediateTransactionManager::new); + return null; + }, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + return null; + }); + } + + @Test + public void live_data_executes_for_a_few_model_queries() { + LiveData> data = QueryLiveData.toLiveData(SQLite.select().from(LiveDataModel.class), (liveDataModelModelQueriable, db) -> { + liveDataModelModelQueriable.queryList(db); + return null; + }); + + data.observe(((Ability)getContext()), Mockito.mock(Observer.class, Mockito.withSettings())); + + List value = data.getValue(); + assert(value.isEmpty()); + + FlowManager.database(TestDatabase.class, testDatabase -> null) + .beginTransactionAsync((Function) db -> { + for(int i=0; i<=2; i++) { + LiveDataModel model = new LiveDataModel(String.valueOf(i), i); + Model.insert(LiveDataModel.class, model, db); + } + return null; + }).execute(null, null, null, null); + + FlowManager.database(TestDatabase.class, testDatabase -> null).tableObserver().checkForTableUpdates(); + + List value2 = data.getValue(); + if(value2.size() == 3) { + System.out.println("expected "+value2.size()+" == 3" ); + } + assert(value2.size() == 3); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/migration/MigrationModels.java b/entry/src/ohosTest/java/com/dbflow5/migration/MigrationModels.java new file mode 100644 index 0000000000000000000000000000000000000000..6ff0b382ada4538680e272c77f7dec073f3f4d36 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/migration/MigrationModels.java @@ -0,0 +1,24 @@ +package com.dbflow5.migration; + +import com.dbflow5.TestDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.annotation.Migration; +import com.dbflow5.migration.BaseMigration; + +public class MigrationModels{ + @Migration(database = TestDatabase.class, priority = 1, version = 1) + public static class FirstMigration extends BaseMigration { + @Override + public void migrate(DatabaseWrapper database) { + + } + } + + @Migration(database = TestDatabase.class, priority = 2, version = 1) + public static class SecondMigration extends BaseMigration{ + @Override + public void migrate(DatabaseWrapper database) { + + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/migration/UpdateTableMigrationTest.java b/entry/src/ohosTest/java/com/dbflow5/migration/UpdateTableMigrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..83ac0e50386f04fc56fcae2d36ac8c4ef2ef1886 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/migration/UpdateTableMigrationTest.java @@ -0,0 +1,16 @@ +package com.dbflow5.migration; + +import com.dbflow5.config.FlowManager; +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import org.junit.Test; + +public class UpdateTableMigrationTest extends BaseUnitTest { + @Test + public void testUpdateMigrationQuery(){ + UpdateTableMigration update=new UpdateTableMigration<>(SimpleTestModels.SimpleModel.class); + update.set(SimpleModel_Table.name.eq("yes")); + update.migrate(FlowManager.databaseForTable(SimpleTestModels.SimpleModel.class, dbFlowDatabase -> null)); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Account_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Account_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..615e87f1114f8835a6ec13adf7931971f5ba3110 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Account_Table.java @@ -0,0 +1,145 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.converter.TypeConverters.UUIDConverter; +import com.dbflow5.models.SimpleTestModels.Account; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import java.util.UUID; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class Account_Table extends ModelAdapter { + /** + * Primary Key */ + public static final TypeConvertedProperty id = new TypeConvertedProperty(Account.class, "id", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Account_Table adapter = (Account_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterUUIDConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + private final UUIDConverter global_typeConverterUUIDConverter; + + public Account_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterUUIDConverter = (UUIDConverter) holder.getTypeConverterForClass(UUID.class); + } + + @Override + public final Class table() { + return Account.class; + } + + @Override + public final String getName() { + return "Account"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "`id`": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Account model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.id); + statement.bindStringOrNull(1, refid); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Account model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.id); + statement.bindStringOrNull(1, refid); + statement.bindStringOrNull(2, refid); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Account model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.id); + statement.bindStringOrNull(1, refid); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Account(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Account(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Account SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Account WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Account(id TEXT, PRIMARY KEY(id))"; + } + + @Override + public final Account loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Account model = new Account(UUID.randomUUID()); + int index_id = cursor.getColumnIndexForName("id"); + if (index_id != -1 && !cursor.isColumnNull(index_id)) { + model.id = global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_id)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Account model) { + OperatorGroup clause = OperatorGroup.clause(); + String refid = global_typeConverterUUIDConverter.getDBValue(model.id); + clause.and(id.invertProperty().eq(refid)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ea340df0ddfd29357800f437ac73445454a33c23 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsModel_Table.java @@ -0,0 +1,139 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.AllFieldsModel; +import java.lang.Boolean; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class AllFieldsModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(AllFieldsModel.class, "name"); + + public static final Property count = new Property(AllFieldsModel.class, "count"); + + public static final Property truth = new Property(AllFieldsModel.class, "truth"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,count,truth}; + + public AllFieldsModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AllFieldsModel.class; + } + + @Override + public final String getName() { + return "AllFieldsModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "count": { + return count; + } + case "truth": { + return truth; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, AllFieldsModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindNumberOrNull(2, model.getCount()); + statement.bindLong(3, model.isTruth() ? 1L : 0L); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, AllFieldsModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindNumberOrNull(2, model.getCount()); + statement.bindLong(3, model.isTruth() ? 1L : 0L); + statement.bindStringOrNull(4, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, AllFieldsModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO AllFieldsModel(name,count,truth) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO AllFieldsModel(name,count,truth) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE AllFieldsModel SET name=?,count=?,truth=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM AllFieldsModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS AllFieldsModel(name TEXT, count INTEGER, truth INTEGER, PRIMARY KEY(name))"; + } + + @Override + public final AllFieldsModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AllFieldsModel model = new AllFieldsModel(null, 0, false, "", 0); + model.setName(cursor.getStringOrDefault("name")); + model.setCount(cursor.getIntOrDefault("count", 0)); + int index_truth = cursor.getColumnIndexForName("truth"); + if (index_truth != -1 && !cursor.isColumnNull(index_truth)) { + model.setTruth(cursor.getBoolean(index_truth)); + } else { + model.setTruth(false); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AllFieldsModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsQueryModel_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsQueryModel_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..15b9bf17030f1ee63dadd41b89a41f65b2d87168 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AllFieldsQueryModel_QueryTable.java @@ -0,0 +1,38 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.QueryModels.AllFieldsQueryModel; +import java.lang.Override; +import java.lang.String; + +public final class AllFieldsQueryModel_QueryTable extends RetrievalAdapter { + public static final Property fieldModel = new Property(AllFieldsQueryModel.class, "fieldModel"); + + public AllFieldsQueryModel_QueryTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AllFieldsQueryModel.class; + } + + @Override + public final AllFieldsQueryModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AllFieldsQueryModel model = new AllFieldsQueryModel(null); + model.setFieldModel(cursor.getStringOrDefault("fieldModel")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AllFieldsQueryModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(fieldModel.eq(model.getFieldModel())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song.java b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song.java new file mode 100644 index 0000000000000000000000000000000000000000..0a291655dbdd361e5b213c2b5e3cc747ebc9faaf --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song.java @@ -0,0 +1,51 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.structure.BaseModel; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +@Table( + database = TestDatabase.class +) +public final class Artist_Song extends BaseModel { + @PrimaryKey( + autoincrement = true + ) + long _id; + + @ForeignKey( + saveForeignKeyModel = false + ) + ManyToMany.Song song; + + @ForeignKey( + saveForeignKeyModel = false + ) + ManyToMany.Artist artist; + + public final long getId() { + return _id; + } + + public final ManyToMany.Song getSong() { + return song; + } + + public final void setSong(ManyToMany.Song param) { + song = param; + } + + public final ManyToMany.Artist getArtist() { + return artist; + } + + public final void setArtist(ManyToMany.Artist param) { + artist = param; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..7e6043b770a593b1cd43753fd6ab253a21847360 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Song_Table.java @@ -0,0 +1,184 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Artist_Song_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property _id = new Property(Artist_Song.class, "_id"); + + /** + * Foreign Key */ + public static final Property song_id = new Property(Artist_Song.class, "song_id"); + + /** + * Foreign Key */ + public static final Property artist_id = new Property(Artist_Song.class, "artist_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{_id,song_id,artist_id}; + + public Artist_Song_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Artist_Song.class; + } + + @Override + public final String getName() { + return "Artist_Song"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "_id": { + return _id; + } + case "song_id": { + return song_id; + } + case "artist_id": { + return artist_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Artist_Song model, Number id) { + model._id = id.longValue(); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Artist_Song model) { + statement.bindLong(1, model._id); + if (model.song != null) { + statement.bindLong(2, (long)model.song.getId()); + } else { + statement.bindNull(2); + } + if (model.artist != null) { + statement.bindLong(3, (long)model.artist.getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Artist_Song model) { + statement.bindLong(1, model._id); + if (model.song != null) { + statement.bindLong(2, (long)model.song.getId()); + } else { + statement.bindNull(2); + } + if (model.artist != null) { + statement.bindLong(3, (long)model.artist.getId()); + } else { + statement.bindNull(3); + } + statement.bindLong(4, model._id); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Artist_Song model) { + statement.bindLong(1, model._id); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Artist_Song(_id,song_id,artist_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Artist_Song(_id,song_id,artist_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Artist_Song SET _id=?,song_id=?,artist_id=? WHERE _id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Artist_Song WHERE _id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Artist_Song(_id INTEGER PRIMARY KEY AUTOINCREMENT, song_id INTEGER, artist_id INTEGER, FOREIGN KEY(song_id) REFERENCES Song (id) ON UPDATE NO ACTION ON DELETE NO ACTION, FOREIGN KEY(artist_id) REFERENCES Artist (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final Artist_Song loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Artist_Song model = new Artist_Song(); + model._id = cursor.getLongOrDefault("_id"); + int index_song_id_Song_Table = cursor.getColumnIndexForName("song_id"); + if (index_song_id_Song_Table != -1 && !cursor.isColumnNull(index_song_id_Song_Table)) { + model.song = SQLite.select().from(ManyToMany.Song.class).where() + .and(Song_Table.id.eq(cursor.getInt(index_song_id_Song_Table))) + .querySingle(wrapper); + } else { + model.song = null; + } + int index_artist_id_Artist_Table = cursor.getColumnIndexForName("artist_id"); + if (index_artist_id_Artist_Table != -1 && !cursor.isColumnNull(index_artist_id_Artist_Table)) { + model.artist = SQLite.select().from(ManyToMany.Artist.class).where() + .and(Artist_Table.id.eq(cursor.getInt(index_artist_id_Artist_Table))) + .querySingle(wrapper); + } else { + model.artist = null; + } + return model; + } + + @Override + public final boolean exists(Artist_Song model, DatabaseWrapper wrapper) { + return model._id > 0 + && SQLite.selectCountOf() + .from(Artist_Song.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Artist_Song model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(_id.eq(model._id)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Artist_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..037ae3ec1ddf04a55c02a00059ab94731f1aa958 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Artist_Table.java @@ -0,0 +1,153 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ManyToMany.Artist; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class Artist_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Artist.class, "id"); + + public static final Property name = new Property(Artist.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public Artist_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Artist.class; + } + + @Override + public final String getName() { + return "Artist"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Artist model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Artist model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Artist model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Artist model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Artist(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Artist(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Artist SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Artist WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Artist(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final Artist loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Artist model = new Artist(0, ""); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final boolean exists(Artist model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Artist.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Artist model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AuthorNameQuery_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/AuthorNameQuery_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..fab588d0e088b61f1b609d63e85a1e842068d7fc --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AuthorNameQuery_QueryTable.java @@ -0,0 +1,47 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.QueryModels.AuthorNameQuery; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class AuthorNameQuery_QueryTable extends RetrievalAdapter { + public static final Property blogName = new Property(AuthorNameQuery.class, "blogName"); + + public static final Property authorId = new Property(AuthorNameQuery.class, "authorId"); + + public static final Property blogId = new Property(AuthorNameQuery.class, "blogId"); + + public AuthorNameQuery_QueryTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AuthorNameQuery.class; + } + + @Override + public final AuthorNameQuery loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AuthorNameQuery model = new AuthorNameQuery("",0,0); + model.setBlogName(cursor.getStringOrDefault("blogName", "")); + model.setAuthorId(cursor.getIntOrDefault("authorId")); + model.setBlogId(cursor.getIntOrDefault("blogId")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AuthorNameQuery model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(blogName.eq(model.getBlogName())); + clause.and(authorId.eq(model.getAuthorId())); + clause.and(blogId.eq(model.getBlogId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AuthorName_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/AuthorName_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..8373b193d81cd912182b514de93391a968005f18 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AuthorName_QueryTable.java @@ -0,0 +1,43 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ModelViews.AuthorName; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class AuthorName_QueryTable extends RetrievalAdapter { + public static final Property name = new Property(AuthorName.class, "name"); + + public static final Property age = new Property(AuthorName.class, "age"); + + public AuthorName_QueryTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AuthorName.class; + } + + @Override + public final AuthorName loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AuthorName model = new AuthorName("", 0); + model.setName(cursor.getStringOrDefault("name", "")); + model.setAge(cursor.getIntOrDefault("age")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AuthorName model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + clause.and(age.eq(model.getAge())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AuthorView_ViewTable.java b/entry/src/ohosTest/java/com/dbflow5/models/AuthorView_ViewTable.java new file mode 100644 index 0000000000000000000000000000000000000000..0ad237b89efa940a902f3c04bdfb2ea53af44b2b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AuthorView_ViewTable.java @@ -0,0 +1,85 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.ModelViewAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ModelViews.AuthorView; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class AuthorView_ViewTable extends ModelViewAdapter { + public static final String VIEW_NAME = "AuthorView"; + + public static final Property authorId = new Property(AuthorView.class, "authorId"); + + public static final Property authorName = new Property(AuthorView.class, "authorName"); + + /** + * Column Mapped Field */ + public static final Property name = new Property(AuthorView.class, "name"); + + /** + * Column Mapped Field */ + public static final Property age = new Property(AuthorView.class, "age"); + + public AuthorView_ViewTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AuthorView.class; + } + + @Override + public final String getCreationQuery() { + return "CREATE VIEW IF NOT EXISTS AuthorView AS " + AuthorView.getQuery().getQuery(); + } + + @Override + public final String getName() { + return "AuthorView"; + } + + @Override + public final ObjectType getType() { + return ObjectType.View; + } + + @Override + public final AuthorView loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AuthorView model = new AuthorView(0,"",null); + model.setAuthorId(cursor.getIntOrDefault("authorId")); + model.setAuthorName(cursor.getStringOrDefault("authorName", "")); + int index_name_AuthorName_QueryTable = cursor.getColumnIndexForName("name"); + int index_age_AuthorName_QueryTable = cursor.getColumnIndexForName("age"); + if (index_name_AuthorName_QueryTable != -1 && !cursor.isColumnNull(index_name_AuthorName_QueryTable) && index_age_AuthorName_QueryTable != -1 && !cursor.isColumnNull(index_age_AuthorName_QueryTable)) { + model.setAuthor(new ModelViews.AuthorName("", 0)); + model.getAuthor().setName(cursor.getString(index_name_AuthorName_QueryTable)); + model.getAuthor().setAge(cursor.getInt(index_age_AuthorName_QueryTable)); + } else { + model.setAuthor(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AuthorView model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(authorId.eq(model.getAuthorId())); + clause.and(authorName.eq(model.getAuthorName())); + if (model.getAuthor() != null) { + clause.and(name.eq(model.getAuthor().getName())); + clause.and(age.eq(model.getAuthor().getAge())); + } else { + clause.and(name.eq((com.dbflow5.query.IConditional) null)); + clause.and(age.eq((com.dbflow5.query.IConditional) null)); + } + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Author_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Author_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..6941235bb57fec8ac043ff11add283fe0e250f3f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Author_Table.java @@ -0,0 +1,165 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.Author; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Author_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property<>(Author.class, "id"); + + public static final Property first_name = new Property<>(Author.class, "first_name"); + + public static final Property last_name = new Property<>(Author.class, "last_name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,first_name,last_name}; + + public Author_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Author.class; + } + + @Override + public final String getName() { + return "Author"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "first_name": { + return first_name; + } + case "last_name": { + return last_name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Author model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Author model) { + statement.bindLong(1, (long)model.getId()); + if (model.getFirstName() != null) { + statement.bindString(2, model.getFirstName()); + } else { + statement.bindString(2, ""); + } + if (model.getLastName() != null) { + statement.bindString(3, model.getLastName()); + } else { + statement.bindString(3, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Author model) { + statement.bindLong(1, (long)model.getId()); + if (model.getFirstName() != null) { + statement.bindString(2, model.getFirstName()); + } else { + statement.bindString(2, ""); + } + if (model.getLastName() != null) { + statement.bindString(3, model.getLastName()); + } else { + statement.bindString(3, ""); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Author model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Author(id,first_name,last_name) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Author(id,first_name,last_name) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE `Author` SET id=?,first_name=?,last_name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Author WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Author(id INTEGER PRIMARY KEY AUTOINCREMENT, first_name TEXT, last_name TEXT)"; + } + + @Override + public final Author loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Author model = new Author(0, "", ""); + model.setId(cursor.getIntOrDefault("id")); + model.setFirstName(cursor.getStringOrDefault("first_name", "")); + model.setLastName(cursor.getStringOrDefault("last_name", "")); + return model; + } + + @Override + public final boolean exists(Author model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Author.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Author model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementTest.java b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b8089b94d6115667f5998e94f3b63e189644e488 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementTest.java @@ -0,0 +1,29 @@ +package com.dbflow5.models; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AutoIncrementTest extends BaseUnitTest { + @Test + public void testCanInsertAutoIncrement() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(AutoIncrementingModel.class); + AutoIncrementingModel model = new AutoIncrementingModel(0); + Model.insert(AutoIncrementingModel.class, model, db); + assertEquals(1L, java.util.Optional.ofNullable(model.id)); + } + + @Test + public void testCanInsertExistingIdAutoIncrement() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(AutoIncrementingModel.class); + AutoIncrementingModel model = new AutoIncrementingModel(3); + Model.insert(AutoIncrementingModel.class, model, db); + assertEquals(3L, java.util.Optional.ofNullable(model.id)); + } +} + diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel.java b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a56d98885f48b8d44079abd5e33ee7791b945e21 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel.java @@ -0,0 +1,23 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +@Table(database = TestDatabase.class) +public class AutoIncrementingModel { + @PrimaryKey(autoincrement = true) + public long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public AutoIncrementingModel(long id) { + this.id = id; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..725069da6c5066be20b8d14d13e37812e5510228 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/AutoIncrementingModel_Table.java @@ -0,0 +1,136 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class AutoIncrementingModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(AutoIncrementingModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public AutoIncrementingModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return AutoIncrementingModel.class; + } + + @Override + public final String getName() { + return "AutoIncrementingModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(AutoIncrementingModel model, Number id) { + model.setId(id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + AutoIncrementingModel model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + AutoIncrementingModel model) { + statement.bindLong(1, model.getId()); + statement.bindLong(2, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + AutoIncrementingModel model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO AutoIncrementingModel(id) VALUES (nullif(?, 0))"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO AutoIncrementingModel(id) VALUES (nullif(?, 0))"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE AutoIncrementingModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM AutoIncrementingModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS AutoIncrementingModel(id INTEGER PRIMARY KEY AUTOINCREMENT)"; + } + + @Override + public final AutoIncrementingModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + AutoIncrementingModel model = new AutoIncrementingModel(0); + model.setId(cursor.getLongOrDefault("id")); + return model; + } + + @Override + public final boolean exists(AutoIncrementingModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(AutoIncrementingModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(AutoIncrementingModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/BlogDeferred_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/BlogDeferred_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..5113ca02b0b35e3e697192d14e1cb0c07cfe266d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/BlogDeferred_Table.java @@ -0,0 +1,174 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.BlogDeferred; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class BlogDeferred_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(BlogDeferred.class, "id"); + + public static final Property name = new Property(BlogDeferred.class, "name"); + + /** + * Foreign Key */ + public static final Property author_id = new Property(BlogDeferred.class, "author_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,author_id}; + + public BlogDeferred_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return BlogDeferred.class; + } + + @Override + public final String getName() { + return "BlogDeferred"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "author_id": { + return author_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(BlogDeferred model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, BlogDeferred model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, BlogDeferred model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, BlogDeferred model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO BlogDeferred(id,name,author_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO BlogDeferred(id,name,author_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE BlogDeferred SET id=?,name=?,author_id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM BlogDeferred WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS BlogDeferred(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, author_id INTEGER, FOREIGN KEY(author_id) REFERENCES Author (id) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)"; + } + + @Override + public final BlogDeferred loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + BlogDeferred model = new BlogDeferred(0,"",null); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + int index_author_id_Author_Table = cursor.getColumnIndexForName("author_id"); + if (index_author_id_Author_Table != -1 && !cursor.isColumnNull(index_author_id_Author_Table)) { + model.setAuthor(SQLite.select().from(ForeignKeyModels.Author.class).where() + .and(Author_Table.id.eq(cursor.getInt(index_author_id_Author_Table))) + .querySingle(wrapper)); + } else { + model.setAuthor(null); + } + return model; + } + + @Override + public final boolean exists(BlogDeferred model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(BlogDeferred.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(BlogDeferred model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/BlogPrimary_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/BlogPrimary_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..f6171729548e6e889ba23cda8374ef370d96adae --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/BlogPrimary_Table.java @@ -0,0 +1,152 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.BlogPrimary; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class BlogPrimary_Table extends ModelAdapter { + /** + * Foreign Key / Primary Key */ + public static final Property author_id = new Property(BlogPrimary.class, "author_id"); + + public static final Property id = new Property(BlogPrimary.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{author_id,id}; + + public BlogPrimary_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return BlogPrimary.class; + } + + @Override + public final String getName() { + return "BlogPrimary"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "author_id": { + return author_id; + } + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, BlogPrimary model) { + if (model.getAuthor() != null) { + statement.bindLong(1, (long)model.getAuthor().getId()); + } else { + statement.bindNull(1); + } + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, BlogPrimary model) { + if (model.getAuthor() != null) { + statement.bindLong(1, (long)model.getAuthor().getId()); + } else { + statement.bindNull(1); + } + statement.bindLong(2, (long)model.getId()); + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, BlogPrimary model) { + if (model.getAuthor() != null) { + statement.bindLong(1, (long)model.getAuthor().getId()); + } else { + statement.bindNull(1); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO BlogPrimary(author_id,id) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO BlogPrimary(author_id,id) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE BlogPrimary SET author_id=?,id=? WHERE author_id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM BlogPrimary WHERE author_id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS BlogPrimary(author_id INTEGER, id INTEGER, PRIMARY KEY(author_id), FOREIGN KEY(author_id) REFERENCES Author (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final BlogPrimary loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + BlogPrimary model = new BlogPrimary(null,0); + int index_author_id_Author_Table = cursor.getColumnIndexForName("author_id"); + if (index_author_id_Author_Table != -1 && !cursor.isColumnNull(index_author_id_Author_Table)) { + model.setAuthor(com.dbflow5.query.SQLite.select().from(ForeignKeyModels.Author.class).where() + .and(Author_Table.id.eq(cursor.getInt(index_author_id_Author_Table))) + .querySingle(wrapper)); + } else { + model.setAuthor(null); + } + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(BlogPrimary model) { + OperatorGroup clause = OperatorGroup.clause(); + if (model.getAuthor() != null) { + clause.and(author_id.eq(model.getAuthor().getId())); + } else { + clause.and(author_id.eq((com.dbflow5.query.IConditional) null)); + } + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/BlogRefNoModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/BlogRefNoModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..c55eb97c34387717e42b37edc20badeff2a83ff7 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/BlogRefNoModel_Table.java @@ -0,0 +1,159 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.BlogRefNoModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class BlogRefNoModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(BlogRefNoModel.class, "id"); + + public static final Property name = new Property(BlogRefNoModel.class, "name"); + + /** + * Foreign Key */ + public static final Property authorId = new Property(BlogRefNoModel.class, "authorId"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,authorId}; + + public BlogRefNoModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return BlogRefNoModel.class; + } + + @Override + public final String getName() { + return "BlogRefNoModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "authorId": { + return authorId; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(BlogRefNoModel model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, BlogRefNoModel model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindStringOrNull(3, model.getAuthorId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, BlogRefNoModel model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindStringOrNull(3, model.getAuthorId()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, BlogRefNoModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO BlogRefNoModel(id,name,authorId) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO BlogRefNoModel(id,name,authorId) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE BlogRefNoModel SET id=?,name=?,authorId=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM BlogRefNoModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS BlogRefNoModel(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, authorId INTEGER NOT NULL ON CONFLICT FAIL, FOREIGN KEY(authorId) REFERENCES Author (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final BlogRefNoModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + BlogRefNoModel model = new BlogRefNoModel(0,"",null); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + model.setAuthorId(cursor.getStringOrDefault("authorId")); + return model; + } + + @Override + public final boolean exists(BlogRefNoModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(BlogRefNoModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(BlogRefNoModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/BlogRef_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/BlogRef_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..658109ab140457c02fa9a822335aa8d0a336eedc --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/BlogRef_Table.java @@ -0,0 +1,171 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.BlogRef; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class BlogRef_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(BlogRef.class, "id"); + + /** + * Primary Key */ + public static final Property name = new Property(BlogRef.class, "name"); + + /** + * Foreign Key */ + public static final Property authorId = new Property(BlogRef.class, "authorId"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,authorId}; + + public BlogRef_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return BlogRef.class; + } + + @Override + public final String getName() { + return "BlogRef"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "authorId": { + return authorId; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, BlogRef model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, BlogRef model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(5, model.getName()); + } else { + statement.bindString(5, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, BlogRef model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO BlogRef(id,name,authorId) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO BlogRef(id,name,authorId) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE BlogRef SET id=?,name=?,authorId=? WHERE id=? AND name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM BlogRef WHERE id=? AND name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS BlogRef(id INTEGER, name TEXT, authorId INTEGER, PRIMARY KEY(id, name), FOREIGN KEY(authorId) REFERENCES Author (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final BlogRef loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + BlogRef model = new BlogRef(0,"",null); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + int index_authorId_Author_Table = cursor.getColumnIndexForName("authorId"); + if (index_authorId_Author_Table != -1 && !cursor.isColumnNull(index_authorId_Author_Table)) { + model.setAuthor(com.dbflow5.query.SQLite.select().from(ForeignKeyModels.Author.class).where() + .and(Author_Table.id.eq(cursor.getInt(index_authorId_Author_Table))) + .querySingle(wrapper)); + } else { + model.setAuthor(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(BlogRef model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/BlogStubbed_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/BlogStubbed_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..506ab2d62fdf020d080f01c561471727c8c577c8 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/BlogStubbed_Table.java @@ -0,0 +1,221 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.BlogStubbed; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class BlogStubbed_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(BlogStubbed.class, "id"); + + public static final Property name = new Property(BlogStubbed.class, "name"); + + /** + * Foreign Key */ + public static final Property author_id = new Property(BlogStubbed.class, "author_id"); + + public static final Property setter = new Property(BlogStubbed.class, "setter"); + + public static final Property getter = new Property(BlogStubbed.class, "getter"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,author_id,setter,getter}; + + public BlogStubbed_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return BlogStubbed.class; + } + + @Override + public final String getName() { + return "BlogStubbed"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "author_id": { + return author_id; + } + case "setter": { + return setter; + } + case "getter": { + return getter; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(BlogStubbed model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final void saveForeignKeys(BlogStubbed model, DatabaseWrapper wrapper) { + if (model.getAuthor() != null) { + FlowManager.getModelAdapter(ForeignKeyModels.Author.class).save(model.getAuthor(), wrapper); + } + } + + @Override + public final void deleteForeignKeys(BlogStubbed model, DatabaseWrapper wrapper) { + if (model.getAuthor() != null) { + FlowManager.getModelAdapter(ForeignKeyModels.Author.class).delete(model.getAuthor(), wrapper); + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, BlogStubbed model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + if (model.getSetter() != null) { + statement.bindString(4, model.getSetter()); + } else { + statement.bindString(4, ""); + } + if (model.getGetter() != null) { + statement.bindString(5, model.getGetter()); + } else { + statement.bindString(5, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, BlogStubbed model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + if (model.getSetter() != null) { + statement.bindString(4, model.getSetter()); + } else { + statement.bindString(4, ""); + } + if (model.getGetter() != null) { + statement.bindString(5, model.getGetter()); + } else { + statement.bindString(5, ""); + } + statement.bindLong(6, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, BlogStubbed model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO BlogStubbed(id,name,author_id,setter,getter) VALUES (nullif(?, 0),?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO BlogStubbed(id,name,author_id,setter,getter) VALUES (nullif(?, 0),?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE BlogStubbed SET id=?,name=?,author_id=?,setter=?,getter=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM BlogStubbed WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS BlogStubbed(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, author_id INTEGER, setter TEXT, getter TEXT, FOREIGN KEY(author_id) REFERENCES Author (id) ON UPDATE RESTRICT ON DELETE CASCADE)"; + } + + @Override + public final BlogStubbed loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + BlogStubbed model = new BlogStubbed(0,"",null,"",""); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + int index_author_id_Author_Table = cursor.getColumnIndexForName("author_id"); + if (index_author_id_Author_Table != -1 && !cursor.isColumnNull(index_author_id_Author_Table)) { + model.setAuthor(new ForeignKeyModels.Author(0,"","")); + model.getAuthor().setId(cursor.getInt(index_author_id_Author_Table)); + } else { + model.setAuthor(null); + } + model.setSetter(cursor.getStringOrDefault("setter", "")); + model.setGetter(cursor.getStringOrDefault("getter", "")); + model.onLoadFromCursor(cursor); + return model; + } + + @Override + public final boolean exists(BlogStubbed model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(BlogStubbed.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(BlogStubbed model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Blog_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Blog_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..a84f03638d3d12830699fd997c6765ed046a81ed --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Blog_Table.java @@ -0,0 +1,182 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.Blog; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Blog_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Blog.class, "id"); + + public static final Property name = new Property(Blog.class, "name"); + + /** + * Foreign Key */ + public static final Property author_id = new Property(Blog.class, "author_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,author_id}; + + public Blog_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Blog.class; + } + + @Override + public final String getName() { + return "Blog"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "author_id": { + return author_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Blog model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final void saveForeignKeys(Blog model, DatabaseWrapper wrapper) { + if (model.getAuthor() != null) { + FlowManager.getModelAdapter(ForeignKeyModels.Author.class).save(model.getAuthor(), wrapper); + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Blog model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Blog model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getAuthor() != null) { + statement.bindLong(3, (long)model.getAuthor().getId()); + } else { + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Blog model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Blog(id,name,author_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Blog(id,name,author_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Blog SET id=?,name=?,author_id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Blog WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Blog(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, author_id INTEGER, FOREIGN KEY(author_id) REFERENCES Author (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final Blog loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Blog model = new Blog(0,"",null); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + int index_author_id_Author_Table = cursor.getColumnIndexForName("author_id"); + if (index_author_id_Author_Table != -1 && !cursor.isColumnNull(index_author_id_Author_Table)) { + model.setAuthor(SQLite.select().from(ForeignKeyModels.Author.class).where() + .and(Author_Table.id.eq(cursor.getInt(index_author_id_Author_Table))) + .querySingle(wrapper)); + } else { + model.setAuthor(null); + } + return model; + } + + @Override + public final boolean exists(Blog model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Blog.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Blog model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/CachingModels.java b/entry/src/ohosTest/java/com/dbflow5/models/CachingModels.java new file mode 100644 index 0000000000000000000000000000000000000000..6f43cd6d0d488bfb4e039ce65e3a5e0e083f8696 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/CachingModels.java @@ -0,0 +1,107 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.MultiCacheField; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.Table; +import com.dbflow5.query.cache.MultiKeyCacheConverter; + +public class CachingModels { + + @Table(database = TestDatabase.class, cachingEnabled = true) + public static class SimpleCacheObject{ + + @PrimaryKey + public String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public SimpleCacheObject(String id) { + this.id = id; + } + } + + @Table(database = TestDatabase.class, cachingEnabled = true) + public static class Coordinate{ + @PrimaryKey + public Double latitude; + @PrimaryKey + public Double longitude; + @ForeignKey + public Path path; + + public Double getLatitude() { + return latitude; + } + + public void setLatitude(Double latitude) { + this.latitude = latitude; + } + + public Double getLongitude() { + return longitude; + } + + public void setLongitude(Double longitude) { + this.longitude = longitude; + } + + public Path getPath() { + return path; + } + + public void setPath(Path path) { + this.path = path; + } + + public static MultiKeyCacheConverter getCacheConverter() { + return cacheConverter; + } + + public Coordinate(Double latitude, Double longitude, Path path) { + this.latitude = latitude; + this.longitude = longitude; + this.path = path; + } + + @MultiCacheField + public static final MultiKeyCacheConverter cacheConverter = (MultiKeyCacheConverter) values -> values[0] + "," + values[1]; + } + + @Table(database = TestDatabase.class) + public static class Path{ + @PrimaryKey + public String id; + @Column + public String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Path(String id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/CachingModelsTest.java b/entry/src/ohosTest/java/com/dbflow5/models/CachingModelsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c672c2ac01f356d22dec8981daad0f892236d9b2 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/CachingModelsTest.java @@ -0,0 +1,55 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.SQLite; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertNotEquals; + +public class CachingModelsTest extends BaseUnitTest { + @Test + public void testSimpleCache() { + FlowManager.database(TestDatabase.class, testDatabase -> { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + CachingModels.SimpleCacheObject simpleCacheObject = new CachingModels.SimpleCacheObject(String.valueOf(i)); + Model.save(CachingModels.SimpleCacheObject.class, simpleCacheObject, testDatabase); + list.add(simpleCacheObject); + } + List loadedList = SQLite.select().from(CachingModels.SimpleCacheObject.class).queryList(testDatabase); + if (loadedList.size() > 0) { + for (int i = 0; i < loadedList.size(); i++) { + Assert.assertEquals(list.get(i), loadedList.get(i)); + } + } + return null; + }); + } + + @Test + public void testComplexObject() { + FlowManager.database(TestDatabase.class, testDatabase -> { + CachingModels.Path path = new CachingModels.Path("1", "Path"); + Model.save(CachingModels.Path.class, path, testDatabase); + + CachingModels.Coordinate coordinate = new CachingModels.Coordinate(40.5, 84.0, path); + Model.save(CachingModels.Coordinate.class, coordinate, testDatabase); + + CachingModels.Path oldPath = coordinate.path; + CachingModels.Coordinate loadedCoordinate = SQLite.select().from(CachingModels.Coordinate.class).querySingle(testDatabase); + Assert.assertEquals(coordinate, loadedCoordinate); + + // we want to ensure relationships reloaded. + assertNotEquals(oldPath, loadedCoordinate.path); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/CharModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/CharModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..3d895c4c797db4f46d8e800c59561c40d1f56aad --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/CharModel_Table.java @@ -0,0 +1,150 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.CharConverter; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.CharModel; +import java.lang.Character; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class CharModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(CharModel.class, "id"); + + public static final TypeConvertedProperty exampleChar = new TypeConvertedProperty(CharModel.class, "exampleChar", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + CharModel_Table adapter = (CharModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterCharConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,exampleChar}; + + private final CharConverter global_typeConverterCharConverter; + + public CharModel_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterCharConverter = (CharConverter) holder.getTypeConverterForClass(Character.class); + } + + @Override + public final Class table() { + return CharModel.class; + } + + @Override + public final String getName() { + return "CharModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "exampleChar": { + return exampleChar; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, CharModel model) { + statement.bindLong(1, (long)model.getId()); + String refexampleChar = global_typeConverterCharConverter.getDBValue(model.getExampleChar()); + statement.bindStringOrNull(2, refexampleChar); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, CharModel model) { + statement.bindLong(1, (long)model.getId()); + String refexampleChar = global_typeConverterCharConverter.getDBValue(model.getExampleChar()); + statement.bindStringOrNull(2, refexampleChar); + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, CharModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO CharModel(id,exampleChar) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO CharModel(id,exampleChar) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE CharModel SET id=?,exampleChar=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM CharModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS CharModel(id INTEGER, exampleChar TEXT, PRIMARY KEY(id))"; + } + + @Override + public final CharModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + CharModel model = new CharModel(0,null); + model.setId(cursor.getIntOrDefault("id")); + int index_exampleChar = cursor.getColumnIndexForName("exampleChar"); + if (index_exampleChar != -1 && !cursor.isColumnNull(index_exampleChar)) { + model.setExampleChar(global_typeConverterCharConverter.getModelValue(cursor.getString(index_exampleChar))); + } else { + model.setExampleChar(global_typeConverterCharConverter.getModelValue(null)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(CharModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Coordinate_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Coordinate_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..02486728ef60f72bbf445b1ab84dbc90b2dea403 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Coordinate_Table.java @@ -0,0 +1,294 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.adapter.queriable.CacheableListModelLoader; +import com.dbflow5.adapter.queriable.CacheableModelLoader; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.CacheableListModelSaver; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.cache.SimpleMapCache; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.CachingModels.Coordinate; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Collection; + +public final class Coordinate_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property latitude = new Property(Coordinate.class, "latitude"); + + /** + * Primary Key */ + public static final Property longitude = new Property(Coordinate.class, "longitude"); + + /** + * Foreign Key */ + public static final Property path_id = new Property(Coordinate.class, "path_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{latitude,longitude,path_id}; + + public static final CacheAdapter cacheAdapter = new CacheAdapter(new SimpleMapCache(25), 2, Coordinate.cacheConverter) { + @Override + public final Object[] getCachingColumnValuesFromModel(Object[] inValues, Coordinate model) { + inValues[0] = model.getLatitude(); + inValues[1] = model.getLongitude(); + return inValues; + } + + @Override + public final Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor) { + inValues[0] = cursor.getDouble(cursor.getColumnIndexForName("latitude")); + inValues[1] = cursor.getDouble(cursor.getColumnIndexForName("longitude")); + return inValues; + } + + @Override + public final void reloadRelationships(Coordinate model, FlowCursor cursor, + DatabaseWrapper wrapper) { + int index_path_id_Path_Table = cursor.getColumnIndexForName("path_id"); + if (index_path_id_Path_Table != -1 && !cursor.isColumnNull(index_path_id_Path_Table)) { + model.setPath(com.dbflow5.query.SQLite.select().from(CachingModels.Path.class).where() + .and(Path_Table.id.eq(cursor.getString(index_path_id_Path_Table))) + .querySingle(wrapper)); + } else { + model.setPath(null); + } + } + + @Override + public Object getCachingColumnValueFromCursor(FlowCursor cursor) { + return null; + } + + @Override + public Object getCachingColumnValueFromModel(Coordinate model) { + return null; + } + }; + + public Coordinate_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Coordinate.class; + } + + @Override + public final String getName() { + return "Coordinate"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "latitude": { + return latitude; + } + case "longitude": { + return longitude; + } + case "path_id": { + return path_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final SingleModelLoader createSingleModelLoader() { + return new CacheableModelLoader<>(table(), cacheAdapter); + } + + @Override + public final ListModelLoader createListModelLoader() { + return new CacheableListModelLoader<>(table(), cacheAdapter); + } + + @Override + protected CacheableListModelSaver createListModelSaver() { + return new CacheableListModelSaver<>(getModelSaver(), cacheAdapter); + } + + @Override + public final boolean cachingEnabled() { + return true; + } + + @Override + public final Coordinate load(Coordinate model, DatabaseWrapper wrapper) { + Coordinate loaded = super.load(model, wrapper); + cacheAdapter.storeModelInCache(model); + return loaded; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Coordinate model) { + statement.bindDouble(1, model.getLatitude()); + statement.bindDouble(2, model.getLongitude()); + if (model.getPath() != null) { + if (model.getPath().getId() != null) { + statement.bindString(3, model.getPath().getId()); + } else { + statement.bindString(3, ""); + } + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Coordinate model) { + statement.bindDouble(1, model.getLatitude()); + statement.bindDouble(2, model.getLongitude()); + if (model.getPath() != null) { + if (model.getPath().getId() != null) { + statement.bindString(3, model.getPath().getId()); + } else { + statement.bindString(3, ""); + } + } else { + statement.bindNull(3); + } + statement.bindDouble(4, model.getLatitude()); + statement.bindDouble(5, model.getLongitude()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Coordinate model) { + statement.bindDouble(1, model.getLatitude()); + statement.bindDouble(2, model.getLongitude()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Coordinate(latitude,longitude,path_id) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Coordinate(latitude,longitude,path_id) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Coordinate SET latitude=?,longitude=?,path_id=? WHERE latitude=? AND longitude=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Coordinate WHERE latitude=? AND longitude=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Coordinate(latitude REAL, longitude REAL, path_id TEXT, PRIMARY KEY(latitude, longitude), FOREIGN KEY(path_id) REFERENCES Path (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final Coordinate loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Coordinate model = new Coordinate(0.0,0.0,null); + model.setLatitude(cursor.getDoubleOrDefault("latitude")); + model.setLongitude(cursor.getDoubleOrDefault("longitude")); + int index_path_id_Path_Table = cursor.getColumnIndexForName("path_id"); + if (index_path_id_Path_Table != -1 && !cursor.isColumnNull(index_path_id_Path_Table)) { + model.setPath(com.dbflow5.query.SQLite.select().from(CachingModels.Path.class).where() + .and(Path_Table.id.eq(cursor.getString(index_path_id_Path_Table))) + .querySingle(wrapper)); + } else { + model.setPath(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Coordinate model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(latitude.eq(model.getLatitude())); + clause.and(longitude.eq(model.getLongitude())); + return clause; + } + + @Override + public final boolean delete(Coordinate model, DatabaseWrapper wrapper) { + cacheAdapter.removeModelFromCache(model); + boolean successful = super.delete(model, wrapper); + return successful; + } + + @Override + public final long deleteAll(Collection models, DatabaseWrapper wrapper) { + cacheAdapter.removeModelsFromCache(models); + long successful = super.deleteAll(models, wrapper); + return successful; + } + + @Override + public final boolean save(Coordinate model, DatabaseWrapper wrapper) { + boolean successful = super.save(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long saveAll(Collection models, DatabaseWrapper wrapper) { + long count = super.saveAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final long insert(Coordinate model, DatabaseWrapper wrapper) { + long rowId = super.insert(model, wrapper); + cacheAdapter.storeModelInCache(model); + return rowId; + } + + @Override + public final long insertAll(Collection models, DatabaseWrapper wrapper) { + long count = super.insertAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final boolean update(Coordinate model, DatabaseWrapper wrapper) { + boolean successful = super.update(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long updateAll(Collection models, DatabaseWrapper wrapper) { + long count = super.updateAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Currency_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Currency_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..6dcea5d23c2e9a1c8896ade43e1c904b9b6f2e2d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Currency_Table.java @@ -0,0 +1,165 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Currency; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Currency_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Currency.class, "id"); + + public static final Property symbol = new Property(Currency.class, "symbol"); + + public static final Property shortName = new Property(Currency.class, "shortName"); + + public static final Property name = new Property(Currency.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,symbol,shortName,name}; + + public Currency_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Currency.class; + } + + @Override + public final String getName() { + return "Currency"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "symbol": { + return symbol; + } + case "shortName": { + return shortName; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Currency model, Number id) { + model.setId(id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Currency model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getSymbol()); + statement.bindStringOrNull(3, model.getShortName()); + if (model.getName() != null) { + statement.bindString(4, model.getName()); + } else { + statement.bindString(4, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Currency model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getSymbol()); + statement.bindStringOrNull(3, model.getShortName()); + if (model.getName() != null) { + statement.bindString(4, model.getName()); + } else { + statement.bindString(4, ""); + } + statement.bindLong(5, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Currency model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Currency(id,symbol,shortName,name) VALUES (nullif(?, 0),?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Currency(id,symbol,shortName,name) VALUES (nullif(?, 0),?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Currency SET id=?,symbol=?,shortName=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Currency WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Currency(id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT UNIQUE ON CONFLICT FAIL, shortName TEXT, name TEXT UNIQUE ON CONFLICT FAIL)"; + } + + @Override + public final Currency loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Currency model = new Currency(0,null,null,""); + model.setId(cursor.getLongOrDefault("id")); + model.setSymbol(cursor.getStringOrDefault("symbol")); + model.setShortName(cursor.getStringOrDefault("shortName")); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final boolean exists(Currency model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Currency.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Currency model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/CustomBlobModel_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/CustomBlobModel_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..f91b699474f03c36c25c2b12d7e7883e53126555 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/CustomBlobModel_QueryTable.java @@ -0,0 +1,59 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.data.Blob; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.QueryModels.CustomBlobModel; +import java.lang.Class; +import java.lang.Override; + +public final class CustomBlobModel_QueryTable extends RetrievalAdapter { + public static final TypeConvertedProperty myBlob = new TypeConvertedProperty(CustomBlobModel.class, "myBlob", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + CustomBlobModel_QueryTable adapter = (CustomBlobModel_QueryTable) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterMyTypeConverter; + } + }); + + private final CustomBlobModel.MyTypeConverter global_typeConverterMyTypeConverter; + + public CustomBlobModel_QueryTable(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterMyTypeConverter = (CustomBlobModel.MyTypeConverter) holder.getTypeConverterForClass(CustomBlobModel.MyBlob.class); + } + + @Override + public final Class table() { + return CustomBlobModel.class; + } + + @Override + public final CustomBlobModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + CustomBlobModel model = new CustomBlobModel(null); + int index_myBlob = cursor.getColumnIndexForName("myBlob"); + if (index_myBlob != -1 && !cursor.isColumnNull(index_myBlob)) { + model.setMyBlob(global_typeConverterMyTypeConverter.getModelValue(new Blob(cursor.getBlob(index_myBlob)))); + } else { + model.setMyBlob(global_typeConverterMyTypeConverter.getModelValue(null)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(CustomBlobModel model) { + OperatorGroup clause = OperatorGroup.clause(); + Blob refmyBlob = global_typeConverterMyTypeConverter.getDBValue(model.getMyBlob()); + clause.and(myBlob.invertProperty().eq(refmyBlob)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/DefaultModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/DefaultModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..5f24c2e4e636caa0f39f9160e19eadb4e720da1c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/DefaultModel_Table.java @@ -0,0 +1,166 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.DefaultModel; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class DefaultModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(DefaultModel.class, "id"); + + public static final Property location = new Property(DefaultModel.class, "location"); + + public static final Property name = new Property(DefaultModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,location,name}; + + public DefaultModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return DefaultModel.class; + } + + @Override + public final String getName() { + return "DefaultModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "location": { + return location; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, DefaultModel model) { + if (model.getId() >= 0) { + statement.bindNumber(1, model.getId()); + } else { + statement.bindLong(1, 5L); + } + if (model.getLocation() != null) { + statement.bindDouble(2, model.getLocation()); + } else { + statement.bindDouble(2, 5.0); + } + if (model.getName() != null) { + statement.bindString(3, model.getName()); + } else { + statement.bindString(3, "String"); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, DefaultModel model) { + if (model.getId() >= 0) { + statement.bindNumber(1, model.getId()); + } else { + statement.bindLong(1, 5L); + } + if (model.getLocation() != null) { + statement.bindDouble(2, model.getLocation()); + } else { + statement.bindDouble(2, 5.0); + } + if (model.getName() != null) { + statement.bindString(3, model.getName()); + } else { + statement.bindString(3, "String"); + } + if (model.getId() >= 0) { + statement.bindNumber(4, model.getId()); + } else { + statement.bindLong(4, 5L); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, DefaultModel model) { + if (model.getId() >= 0) { + statement.bindNumber(1, model.getId()); + } else { + statement.bindLong(1, 5L); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO DefaultModel(id,location,name) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO DefaultModel(id,location,name) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE DefaultModel SET id=?,location=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM DefaultModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS DefaultModel(id INTEGER, location REAL, name TEXT, PRIMARY KEY(id))"; + } + + @Override + public final DefaultModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + DefaultModel model = new DefaultModel(0, 0.0, ""); + model.setId(cursor.getIntOrDefault("id", 5)); + model.setLocation(cursor.getDoubleOrDefault("location", 5.0)); + model.setName(cursor.getStringOrDefault("name", "String")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(DefaultModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Dog_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Dog_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..e3af9701fba2d8035923aa7c6a202404199b4246 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Dog_Table.java @@ -0,0 +1,165 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Dog; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Dog_Table extends ModelAdapter { + /** + * Foreign Key */ + public static final Property owner_id = new Property(Dog.class, "owner_id"); + + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Dog.class, "id"); + + public static final Property name = new Property(Dog.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{owner_id,id,name}; + + public Dog_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Dog.class; + } + + @Override + public final String getName() { + return "Dog"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "owner_id": { + return owner_id; + } + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Dog model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Dog model) { + if (model.getOwner() != null) { + statement.bindLong(1, (long)model.getOwner().getId()); + } else { + statement.bindNull(1); + } + statement.bindLong(2, (long)model.getId()); + statement.bindStringOrNull(3, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Dog model) { + if (model.getOwner() != null) { + statement.bindLong(1, (long)model.getOwner().getId()); + } else { + statement.bindNull(1); + } + statement.bindLong(2, (long)model.getId()); + statement.bindStringOrNull(3, model.getName()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Dog model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Dog(owner_id,id,name) VALUES (?,nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Dog(owner_id,id,name) VALUES (?,nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Dog SET owner_id=?,id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Dog WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Dog(owner_id INTEGER, id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, FOREIGN KEY(owner_id) REFERENCES Owner (id) ON UPDATE NO ACTION ON DELETE CASCADE)"; + } + + @Override + public final Dog loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Dog model = new Dog(null,0,null); + int index_owner_id_Owner_Table = cursor.getColumnIndexForName("owner_id"); + if (index_owner_id_Owner_Table != -1 && !cursor.isColumnNull(index_owner_id_Owner_Table)) { + model.setOwner(new SimpleTestModels.Owner(0,null)); + model.getOwner().setId(cursor.getInt(index_owner_id_Owner_Table)); + } else { + model.setOwner(null); + } + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final boolean exists(Dog model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Dog.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Dog model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/DontAssignDefaultModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/DontAssignDefaultModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..3f146eab945c2ec8d1fc8aacdbac6b42cf05a397 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/DontAssignDefaultModel_Table.java @@ -0,0 +1,159 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.BooleanConverter; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.DontAssignDefaultModel; +import java.lang.Boolean; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class DontAssignDefaultModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(DontAssignDefaultModel.class, "name"); + + public static final TypeConvertedProperty nullableBool = new TypeConvertedProperty(DontAssignDefaultModel.class, "nullableBool", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + DontAssignDefaultModel_Table adapter = (DontAssignDefaultModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterBooleanConverter; + } + }); + + public static final Property index = new Property(DontAssignDefaultModel.class, "_index"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,nullableBool,index}; + + private final BooleanConverter global_typeConverterBooleanConverter; + + public DontAssignDefaultModel_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterBooleanConverter = (BooleanConverter) holder.getTypeConverterForClass(Boolean.class); + } + + @Override + public final Class table() { + return DontAssignDefaultModel.class; + } + + @Override + public final String getName() { + return "DontAssignDefaultModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "nullableBool": { + return nullableBool; + } + case "_index": { + return index; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + DontAssignDefaultModel model) { + statement.bindStringOrNull(1, model.getName()); + Integer refnullableBool = global_typeConverterBooleanConverter.getDBValue(model.nullableBool); + statement.bindNumberOrNull(2, refnullableBool); + statement.bindLong(3, (long)model.getIndex()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + DontAssignDefaultModel model) { + statement.bindStringOrNull(1, model.getName()); + Integer refnullableBool = global_typeConverterBooleanConverter.getDBValue(model.nullableBool); + statement.bindNumberOrNull(2, refnullableBool); + statement.bindLong(3, (long)model.getIndex()); + statement.bindStringOrNull(4, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + DontAssignDefaultModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO DontAssignDefaultModel(name,nullableBool,_index) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO DontAssignDefaultModel(name,nullableBool,_index) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE DontAssignDefaultModel SET name=?,nullableBool=?,_index=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM DontAssignDefaultModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS DontAssignDefaultModel(name TEXT, nullableBool INTEGER, _index INTEGER, PRIMARY KEY(name))"; + } + + @Override + public final DontAssignDefaultModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + DontAssignDefaultModel model = new DontAssignDefaultModel(null, false, 0); + model.setName(cursor.getStringOrDefault("name")); + int index_nullableBool = cursor.getColumnIndexForName("nullableBool"); + if (index_nullableBool != -1 && !cursor.isColumnNull(index_nullableBool)) { + model.setNullableBool(global_typeConverterBooleanConverter.getModelValue(cursor.getInt(index_nullableBool))); + } + model.setIndex(cursor.getIntOrDefault("_index")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(DontAssignDefaultModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModelTest.java b/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1f15a99f896a5bd1b29ac33c74cefe7e10224868 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModelTest.java @@ -0,0 +1,24 @@ +package com.dbflow5.models; + +import com.dbflow5.TestExtensions; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.SQLiteException; +import com.dbflow5.query.SQLite; + +import com.dbflow5.BaseUnitTest; +import org.junit.Test; + +import java.util.function.Function; + +public class DontCreateModelTest extends BaseUnitTest { + @Test + public void testModelNotCreated() { + FlowManager.databaseForTable(SimpleTestModels.DontCreateModel.class, dbFlowDatabase -> { + TestExtensions.assertThrowsException(SQLiteException.class, unused -> { + SQLite.select().from(SimpleTestModels.DontCreateModel.class).queryList(dbFlowDatabase); + return null; + }); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..320f119c68fe0be1a4db93a8dd3d687ef2b1bb21 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/DontCreateModel_Table.java @@ -0,0 +1,123 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.DontCreateModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + + +public final class DontCreateModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(DontCreateModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public DontCreateModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return DontCreateModel.class; + } + + @Override + public final String getName() { + return "DontCreateModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final boolean createWithDatabase() { + return false; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, DontCreateModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, DontCreateModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, DontCreateModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO DontCreateModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO DontCreateModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE DontCreateModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM DontCreateModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS DontCreateModel(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final DontCreateModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + DontCreateModel model = new DontCreateModel(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(DontCreateModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/EnumModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/EnumModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..d4d74a3f14ab10abba5ea7f8b986aab4930cad13 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/EnumModel_Table.java @@ -0,0 +1,138 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.WrapperProperty; +import com.dbflow5.models.SimpleTestModels.EnumModel; +import com.dbflow5.models.SimpleTestModels.Difficulty; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class EnumModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(EnumModel.class, "id"); + + public static final WrapperProperty difficulty = new WrapperProperty(EnumModel.class, "difficulty"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,difficulty}; + + public EnumModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return EnumModel.class; + } + + @Override + public final String getName() { + return "EnumModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "difficulty": { + return difficulty; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, EnumModel model) { + statement.bindLong(1, (long)model.getId()); + String refdifficulty = model.getDifficulty() != null ? model.getDifficulty().name() : null; + statement.bindStringOrNull(2, refdifficulty); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, EnumModel model) { + statement.bindLong(1, (long)model.getId()); + String refdifficulty = model.getDifficulty() != null ? model.getDifficulty().name() : null; + statement.bindStringOrNull(2, refdifficulty); + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, EnumModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO EnumModel(id,difficulty) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO EnumModel(id,difficulty) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE EnumModel SET id=?,difficulty=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM EnumModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS EnumModel(id INTEGER, difficulty TEXT, PRIMARY KEY(id))"; + } + + @Override + public final EnumModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + EnumModel model = new EnumModel(0, Difficulty.EASY); + model.setId(cursor.getIntOrDefault("id")); + int index_difficulty = cursor.getColumnIndexForName("difficulty"); + if (index_difficulty != -1 && !cursor.isColumnNull(index_difficulty)) { + try { + model.setDifficulty(Difficulty.valueOf(cursor.getString(index_difficulty))); + } catch (IllegalArgumentException e) { + model.setDifficulty(null); + } + } else { + model.setDifficulty(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(EnumModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/EnumTypeConverterModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/EnumTypeConverterModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ed137c4e4b483db27c8b4c19588ba921b853e323 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/EnumTypeConverterModel_Table.java @@ -0,0 +1,174 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.data.Blob; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.query.property.WrapperProperty; +import com.dbflow5.models.SimpleTestModels.EnumTypeConverterModel; +import com.dbflow5.models.SimpleTestModels.Difficulty; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + + +public final class EnumTypeConverterModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(EnumTypeConverterModel.class, "id"); + + public static final WrapperProperty blob = new WrapperProperty(EnumTypeConverterModel.class, "blob"); + + public static final Property byteArray = new Property(EnumTypeConverterModel.class, "byteArray"); + + public static final TypeConvertedProperty difficulty = new TypeConvertedProperty(EnumTypeConverterModel.class, "difficulty", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + EnumTypeConverterModel_Table adapter = (EnumTypeConverterModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.typeConverterCustomEnumTypeConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,blob,byteArray,difficulty}; + + private final SimpleTestModels.CustomEnumTypeConverter typeConverterCustomEnumTypeConverter = new SimpleTestModels.CustomEnumTypeConverter(); + + public EnumTypeConverterModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return EnumTypeConverterModel.class; + } + + @Override + public final String getName() { + return "EnumTypeConverterModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "blob": { + return blob; + } + case "byteArray": { + return byteArray; + } + case "difficulty": { + return difficulty; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + EnumTypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + byte[] refblob = model.getBlob() != null ? model.getBlob().getBlob() : null; + statement.bindBlobOrNull(2, refblob); + statement.bindBlobOrNull(3, model.getByteArray()); + String refdifficulty = typeConverterCustomEnumTypeConverter.getDBValue(model.getDifficulty()); + statement.bindStringOrNull(4, refdifficulty); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + EnumTypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + byte[] refblob = model.getBlob() != null ? model.getBlob().getBlob() : null; + statement.bindBlobOrNull(2, refblob); + statement.bindBlobOrNull(3, model.getByteArray()); + String refdifficulty = typeConverterCustomEnumTypeConverter.getDBValue(model.getDifficulty()); + statement.bindStringOrNull(4, refdifficulty); + statement.bindLong(5, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + EnumTypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO EnumTypeConverterModel(id,blob,byteArray,difficulty) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO EnumTypeConverterModel(id,blob,byteArray,difficulty) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE EnumTypeConverterModel SET id=?,blob=?,byteArray=?,difficulty=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM EnumTypeConverterModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS EnumTypeConverterModel(id INTEGER, blob BLOB, byteArray BLOB, difficulty TEXT, PRIMARY KEY(id))"; + } + + @Override + public final EnumTypeConverterModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + EnumTypeConverterModel model = new EnumTypeConverterModel(0,null,null,Difficulty.EASY); + model.setId(cursor.getIntOrDefault("id")); + int index_blob = cursor.getColumnIndexForName("blob"); + if (index_blob != -1 && !cursor.isColumnNull(index_blob)) { + model.setBlob(new Blob(cursor.getBlob(index_blob))); + } else { + model.setBlob(null); + } + model.setByteArray(cursor.getBlobOrDefault("byteArray")); + int index_difficulty = cursor.getColumnIndexForName("difficulty"); + if (index_difficulty != -1 && !cursor.isColumnNull(index_difficulty)) { + model.setDifficulty(typeConverterCustomEnumTypeConverter.getModelValue(cursor.getString(index_difficulty))); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(EnumTypeConverterModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/FeedEntry_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/FeedEntry_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..edba76b875c209e11d8ceee762366f8fcc1b4da9 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/FeedEntry_Table.java @@ -0,0 +1,133 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.FeedEntry; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class FeedEntry_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(FeedEntry.class, "id"); + + public static final Property title = new Property(FeedEntry.class, "title"); + + public static final Property subtitle = new Property(FeedEntry.class, "subtitle"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,title,subtitle}; + + public FeedEntry_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return FeedEntry.class; + } + + @Override + public final String getName() { + return "FeedEntry"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "title": { + return title; + } + case "subtitle": { + return subtitle; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, FeedEntry model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getTitle()); + statement.bindStringOrNull(3, model.getSubtitle()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, FeedEntry model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getTitle()); + statement.bindStringOrNull(3, model.getSubtitle()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, FeedEntry model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO FeedEntry(id,title,subtitle) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO FeedEntry(id,title,subtitle) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE FeedEntry SET id=?,title=?,subtitle=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM FeedEntry WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS FeedEntry(id INTEGER, title TEXT, subtitle TEXT, PRIMARY KEY(id))"; + } + + @Override + public final FeedEntry loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + FeedEntry model = new FeedEntry(0,null,null); + model.setId(cursor.getIntOrDefault("id")); + model.setTitle(cursor.getStringOrDefault("title")); + model.setSubtitle(cursor.getStringOrDefault("subtitle")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(FeedEntry model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ForeignKeyModels.java b/entry/src/ohosTest/java/com/dbflow5/models/ForeignKeyModels.java new file mode 100644 index 0000000000000000000000000000000000000000..632c85f50144f3582b9a10a74a3e26ba4a7eeff9 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ForeignKeyModels.java @@ -0,0 +1,530 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.*; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.LoadFromCursorListener; + +public class ForeignKeyModels { + + /** + * Example of simple foreign key object with one foreign key object. + */ + @Table(database = TestDatabase.class) + public static class Blog{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + @ForeignKey(saveForeignKeyModel = true) + public Author author; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Blog(int id, String name, Author author) { + this.id = id; + this.name = name; + this.author = author; + } + } + + /** + * Parent used as foreign key reference. + */ + @Table(database = TestDatabase.class) + public static class Author{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String firstName; + @Column + public String lastName; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Author(int id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + } + + /** + * Example of simple foreign key object with its [ForeignKey] deferred. + */ + @Table(database = TestDatabase.class) + public static class BlogDeferred{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + @ForeignKey(deferred = true) + public Author author; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public BlogDeferred(int id, String name, Author author) { + this.id = id; + this.name = name; + this.author = author; + } + } + + /** + * Class has example of single foreign key with [ForeignKeyReference] specified + */ + @Table(database = TestDatabase.class) + public static class BlogRef{ + @PrimaryKey + public int id; + @PrimaryKey + public String name; + @ForeignKey(references = {@ForeignKeyReference(columnName = "authorId", foreignKeyColumnName = "id", defaultValue = "not gonna work")}) + public Author author; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public BlogRef(int id, String name, Author author) { + this.id = id; + this.name = name; + this.author = author; + } + } + + /** + * Class has example of single foreign key with [ForeignKeyReference] specified that is not the model object. + */ + @Table(database = TestDatabase.class) + public static class BlogRefNoModel{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + @ForeignKey(references = {@ForeignKeyReference(columnName = "authorId", foreignKeyColumnName = "id", notNull = @NotNull(onNullConflict = ConflictAction.FAIL))}, + tableClass = Author.class) + public String authorId; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthorId() { + return authorId; + } + + public void setAuthorId(String authorId) { + this.authorId = authorId; + } + + public BlogRefNoModel(int id, String name, String authorId) { + this.id = id; + this.name = name; + this.authorId = authorId; + } + } + + /** + * Class has example of single foreign key with [ForeignKeyReference] as [PrimaryKey] + */ + @Table(database = TestDatabase.class) + public static class BlogPrimary{ + @PrimaryKey @ForeignKey + public Author author; + @Column + public int id; + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public BlogPrimary(Author author, int id) { + this.author = author; + this.id = id; + } + } + + /** + * Example of simple foreign key object with one foreign key object thats [ForeignKey.stubbedRelationship] + * and [ForeignKey.deleteForeignKeyModel] and [ForeignKey.saveForeignKeyModel] + */ + @Table(database = TestDatabase.class) + public static class BlogStubbed implements LoadFromCursorListener { + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + @ForeignKey(stubbedRelationship = true, deleteForeignKeyModel = true, saveForeignKeyModel = true, + onDelete = ForeignKeyAction.CASCADE, onUpdate = ForeignKeyAction.RESTRICT) + public Author author; + public String setter; + public String getter; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public String getSetter() { + return setter; + } + + public void setSetter(String setter) { + this.setter = setter; + } + + public String getGetter() { + return getter; + } + + public void setGetter(String getter) { + this.getter = getter; + } + + public BlogStubbed(int id, String name, Author author, String setter, String getter) { + this.id = id; + this.name = name; + this.author = author; + this.setter = setter; + this.getter = getter; + } + + @Override + public void onLoadFromCursor(FlowCursor cursor) { + } + } + + public static class DoubleToDouble{ + public double _double; + + public DoubleToDouble(double _double) { + this._double = _double; + } + } + + @TypeConverter + public static class DoubleConverter extends TypeConverters.TypeConverter { + + public DoubleConverter() { + super(); + } + + @Override + public Double getDBValue(DoubleToDouble model) { + return model != null? model._double : null; + } + + @Override + public DoubleToDouble getModelValue(Double data) { + if(data != null) { + return new DoubleToDouble(data); + } + return null; + } + } + + public static class Location{ + public DoubleToDouble latitude; + public DoubleToDouble longitude; + + public DoubleToDouble getLatitude() { + return latitude; + } + + public void setLatitude(DoubleToDouble latitude) { + this.latitude = latitude; + } + + public DoubleToDouble getLongitude() { + return longitude; + } + + public void setLongitude(DoubleToDouble longitude) { + this.longitude = longitude; + } + + public Location(DoubleToDouble latitude, DoubleToDouble longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + } + + @Table(database = TestDatabase.class) + public static class Position{ + @PrimaryKey + public int id; + @ColumnMap + public Location location; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public Position(int id, Location location) { + this.id = id; + this.location = location; + } + } + + @Table(database = TestDatabase.class) + public static class Position2{ + @PrimaryKey + public int id; + @ColumnMap(references = { + @ColumnMapReference(columnName = "latitude", columnMapFieldName = "latitude", defaultValue = "40.6"), + @ColumnMapReference(columnName = "longitude", columnMapFieldName = "longitude", defaultValue = "55.5")}) + public Location location; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public Position2(int id, Location location) { + this.id = id; + this.location = location; + } + } + + public static class Location2{ + public DoubleToDouble latitude; + public Double longitude; + + public DoubleToDouble getLatitude() { + return latitude; + } + + public void setLatitude(DoubleToDouble latitude) { + this.latitude = latitude; + } + + public Double getLongitude() { + return longitude; + } + + public void setLongitude(Double longitude) { + this.longitude = longitude; + } + + public Location2(DoubleToDouble latitude, Double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + } + + @Table(database = TestDatabase.class) + public static class PositionWithTypeConverter{ + @PrimaryKey + public int id; + @ColumnMap(references = {@ColumnMapReference(columnName = "latitude", + columnMapFieldName = "latitude", typeConverter = DoubleConverter.class), + @ColumnMapReference(columnName = "longitude", columnMapFieldName = "longitude")}) + public Location2 location; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Location2 getLocation() { + return location; + } + + public void setLocation(Location2 location) { + this.location = location; + } + + public PositionWithTypeConverter(int id, Location2 location) { + this.id = id; + this.location = location; + } + } + + @Table(database = TestDatabase.class) + public static class NotNullReferenceModel{ + @PrimaryKey + public String name; + @NotNull @ForeignKey + public SimpleTestModels.SimpleModel model; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SimpleTestModels.SimpleModel getModel() { + return model; + } + + public void setModel(SimpleTestModels.SimpleModel model) { + this.model = model; + } + + public NotNullReferenceModel(String name, SimpleTestModels.SimpleModel model) { + this.name = name; + this.model = model; + } + } +} + + diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Fts3Model_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Fts3Model_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..052c362072d4717790e4696d05e93dda940ae07a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Fts3Model_Table.java @@ -0,0 +1,134 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Fts3Model; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class Fts3Model_Table extends ModelAdapter { + public static final Property name = new Property(Fts3Model.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public Fts3Model_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Fts3Model.class; + } + + @Override + public final String getName() { + return "Fts3Model"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Fts3Model model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Fts3Model model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Fts3Model model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Fts3Model(name) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Fts3Model(name) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Fts3Model SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Fts3Model WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE VIRTUAL TABLE IF NOT EXISTS Fts3Model USING FTS3(name)"; + } + + @Override + public final Fts3Model loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Fts3Model model = new Fts3Model(""); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Fts3Model model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Fts4Model_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Fts4Model_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..aca4497a5fe073e127a1394815acc5fbe62aeecf --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Fts4Model_Table.java @@ -0,0 +1,153 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Fts4Model; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class Fts4Model_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Fts4Model.class, "id"); + + public static final Property name = new Property(Fts4Model.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public Fts4Model_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Fts4Model.class; + } + + @Override + public final String getName() { + return "Fts4Model"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Fts4Model model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Fts4Model model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Fts4Model model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Fts4Model model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Fts4Model(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Fts4Model(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Fts4Model SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Fts4Model WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Fts4Model(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final Fts4Model loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Fts4Model model = new Fts4Model(0,""); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final boolean exists(Fts4Model model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Fts4Model.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Fts4Model model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Fts4VirtualModel2_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Fts4VirtualModel2_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..c237a254370c0ed7473ada2dc1ee6e2477d19014 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Fts4VirtualModel2_Table.java @@ -0,0 +1,134 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Fts4VirtualModel2; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class Fts4VirtualModel2_Table extends ModelAdapter { + public static final Property name = new Property(Fts4VirtualModel2.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public Fts4VirtualModel2_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Fts4VirtualModel2.class; + } + + @Override + public final String getName() { + return "Fts4VirtualModel2"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Fts4VirtualModel2 model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Fts4VirtualModel2 model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Fts4VirtualModel2 model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Fts4VirtualModel2(name) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Fts4VirtualModel2(name) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Fts4VirtualModel2 SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Fts4VirtualModel2 WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE VIRTUAL TABLE IF NOT EXISTS Fts4VirtualModel2 USING FTS4(name, content=Fts4Model)"; + } + + @Override + public final Fts4VirtualModel2 loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Fts4VirtualModel2 model = new Fts4VirtualModel2(""); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Fts4VirtualModel2 model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/FtsModelTest.java b/entry/src/ohosTest/java/com/dbflow5/models/FtsModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b1d8d1cc2f61bd4112f75628124560ef8acc4c88 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/FtsModelTest.java @@ -0,0 +1,83 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.Method; +import com.dbflow5.query.SQLite; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.property.PropertyFactory; +import com.dbflow5.structure.Model; +import org.junit.Test; + +import static com.dbflow5.query.property.PropertyFactory.docId; + +public class FtsModelTest extends BaseUnitTest { + + @Test + public void validate_fts4_created() { + FlowManager.database(TestDatabase.class, testDatabase -> { + SimpleTestModels.Fts4Model model = new SimpleTestModels.Fts4Model(0, "FTSBABY"); + Model.save(SimpleTestModels.Fts4Model.class, model, testDatabase); + + long rows = SQLite.insert(SimpleTestModels.Fts4VirtualModel2.class, docId, Fts4VirtualModel2_Table.name) + .select(SQLite.select(Fts4Model_Table.id, Fts4Model_Table.name) + .from(SimpleTestModels.Fts4Model.class)) + .executeInsert(testDatabase); + assert (rows > 0); + return null; + }); + } + + @Test + public void match_query() { + validate_fts4_created(); + FlowManager.database(TestDatabase.class, testDatabase -> { + SimpleTestModels.Fts4VirtualModel2 model = SQLite.select() + .from(SimpleTestModels.Fts4VirtualModel2.class) + .where(PropertyFactory.tableName(SimpleTestModels.Fts4VirtualModel2.class).match("FTSBABY")).querySingle(testDatabase); + assert (model != null); + return null; + }); + } + + @Test + public void offsets_query() { + validate_fts4_created(); + FlowManager.database(TestDatabase.class, testDatabase -> { + String value = SQLite.select(Method.offsets(SimpleTestModels.Fts4VirtualModel2.class)) + .from(SimpleTestModels.Fts4VirtualModel2.class) + .where(PropertyFactory.tableName(SimpleTestModels.Fts4VirtualModel2.class) + .match("FTSBaby")).stringValue(testDatabase); + assert (value != null); + assert (value.equals("0 0 0 7")); + return null; + }); + } + + @Test + public void snippet_query() { + FlowManager.database(TestDatabase.class, db -> { + SimpleTestModels.Fts4Model model = new SimpleTestModels.Fts4Model(0, "During 30 Nov-1 Dec, 2-3oC drops. Cool in the upper portion, minimum temperature 14-16oC \n" + + " and cool elsewhere, minimum temperature 17-20oC. Cold to very cold on mountaintops, \n" + + " minimum temperature 6-12oC. Northeasterly winds 15-30 km/hr. After that, temperature \n" + + " increases. Northeasterly winds 15-30 km/hr. "); + Model.save(SimpleTestModels.Fts4Model.class, model, db); + + long rows = SQLite.insert(SimpleTestModels.Fts4VirtualModel2.class, docId, Fts4VirtualModel2_Table.name) + .select(SQLite.select(Fts4Model_Table.id, Fts4Model_Table.name) + .from(SimpleTestModels.Fts4Model.class)) + .executeInsert(db); + assert (rows > 0); + String value = SQLite.select(Method.snippet(SimpleTestModels.Fts4VirtualModel2.class, "[", "]", "...", 0, 0)) + .from(SimpleTestModels.Fts4VirtualModel2.class) + .where(PropertyFactory.tableName(SimpleTestModels.Fts4VirtualModel2.class) + .match("\"min* tem*\"")) + .stringValue(db); + assert (value != null); + assert (value.equals("...the upper portion, [minimum] [temperature] 14-16oC \n" + + " and cool elsewhere, [minimum] [temperature] 17-20oC. Cold...")); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/IndexModel.java b/entry/src/ohosTest/java/com/dbflow5/models/IndexModel.java new file mode 100644 index 0000000000000000000000000000000000000000..25e27b62a15a268531249af06dbd12d0ed26eb73 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/IndexModel.java @@ -0,0 +1,87 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.Index; +import com.dbflow5.annotation.IndexGroup; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import java.util.*; + +/** + * Description: + */ +@Table(database = TestDatabase.class, + indexGroups = {@IndexGroup(number = 1, name = "firstIndex"), + @IndexGroup(number = 2, name = "secondIndex"), + @IndexGroup(number = 3, name = "thirdIndex")} + ) +public class IndexModel { + @Index(indexGroups = {1, 2, 3}) + @PrimaryKey + public int id; + + @Index(indexGroups = {1}) + @Column + public String first_name; + + @Index(indexGroups = {2}) + @Column + public String last_name; + + @Index(indexGroups = {3}) + @Column + public Date created_date; + + @Index(indexGroups = {2, 3}) + @Column + public boolean isPro; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirst_name() { + return first_name; + } + + public void setFirst_name(String first_name) { + this.first_name = first_name; + } + + public String getLast_name() { + return last_name; + } + + public void setLast_name(String last_name) { + this.last_name = last_name; + } + + public Date getCreated_date() { + return created_date; + } + + public void setCreated_date(Date created_date) { + this.created_date = created_date; + } + + public boolean isPro() { + return isPro; + } + + public void setPro(boolean pro) { + isPro = pro; + } + + public IndexModel(int id, String first_name, String last_name, Date created_date, boolean isPro) { + this.id = id; + this.first_name = first_name; + this.last_name = last_name; + this.created_date = created_date; + this.isPro = isPro; + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/dbflow5/models/IndexModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/IndexModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..103495b7aa4227a1967386a9a2baebbac3a094f1 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/IndexModel_Table.java @@ -0,0 +1,187 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.DateConverter; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.IndexProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import java.lang.Boolean; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; +import java.util.Date; + +public final class IndexModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(IndexModel.class, "id"); + + public static final Property first_name = new Property(IndexModel.class, "first_name"); + + public static final Property last_name = new Property(IndexModel.class, "last_name"); + + public static final TypeConvertedProperty created_date = new TypeConvertedProperty(IndexModel.class, "created_date", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + IndexModel_Table adapter = (IndexModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterDateConverter; + } + }); + + public static final Property isPro = new Property(IndexModel.class, "isPro"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,first_name,last_name,created_date,isPro}; + + public static final IndexProperty index_firstIndex = new IndexProperty<>("firstIndex", false, IndexModel.class,id, first_name); + + public static final IndexProperty index_secondIndex = new IndexProperty<>("secondIndex", false, IndexModel.class,id, last_name, isPro); + + public static final IndexProperty index_thirdIndex = new IndexProperty<>("thirdIndex", false, IndexModel.class,id, created_date, isPro); + + private final DateConverter global_typeConverterDateConverter; + + public IndexModel_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDateConverter = (DateConverter) holder.getTypeConverterForClass(Date.class); + } + + @Override + public final Class table() { + return IndexModel.class; + } + + @Override + public final String getName() { + return "IndexModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "first_name": { + return first_name; + } + case "last_name": { + return last_name; + } + case "created_date": { + return created_date; + } + case "isPro": { + return isPro; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, IndexModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getFirst_name()); + statement.bindStringOrNull(3, model.getLast_name()); + Long refcreated_date = global_typeConverterDateConverter.getDBValue(model.getCreated_date()); + statement.bindNumberOrNull(4, refcreated_date); + statement.bindLong(5, model.isPro() ? 1L : 0L); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, IndexModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getFirst_name()); + statement.bindStringOrNull(3, model.getLast_name()); + Long refcreated_date = global_typeConverterDateConverter.getDBValue(model.getCreated_date()); + statement.bindNumberOrNull(4, refcreated_date); + statement.bindLong(5, model.isPro() ? 1L : 0L); + statement.bindLong(6, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, IndexModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO IndexModel(id,first_name,last_name,created_date,isPro) VALUES (?,?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO IndexModel(id,first_name,last_name,created_date,isPro) VALUES (?,?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE IndexModel SET id=?,first_name=?,last_name=?,created_date=?,isPro=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM IndexModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS IndexModel(id INTEGER, first_name TEXT, last_name TEXT, created_date INTEGER, isPro INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final IndexModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + IndexModel model = new IndexModel(0,null,null,null,false); + model.setId(cursor.getIntOrDefault("id")); + model.setFirst_name(cursor.getStringOrDefault("first_name")); + model.setLast_name(cursor.getStringOrDefault("last_name")); + int index_created_date = cursor.getColumnIndexForName("created_date"); + if (index_created_date != -1 && !cursor.isColumnNull(index_created_date)) { + model.setCreated_date(global_typeConverterDateConverter.getModelValue(cursor.getLong(index_created_date))); + } else { + model.setCreated_date(global_typeConverterDateConverter.getModelValue(null)); + } + int index_isPro = cursor.getColumnIndexForName("isPro"); + if (index_isPro != -1 && !cursor.isColumnNull(index_isPro)) { + model.setPro(cursor.getBoolean(index_isPro)); + } else { + model.setPro(false); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(IndexModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Inner_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Inner_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..3d95eefd1cfa6d6f3c7da2aed4f32fb75b8fa910 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Inner_Table.java @@ -0,0 +1,117 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class Inner_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Outer.Inner.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public Inner_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Outer.Inner.class; + } + + @Override + public final String getName() { + return "Inner"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Outer.Inner model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Outer.Inner model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Outer.Inner model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Inner(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Inner(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Inner SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Inner WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Inner(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final Outer.Inner loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Outer.Inner model = new Outer.Inner(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Outer.Inner model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/InternalClass_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/InternalClass_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ed1fd65c1bbc08ff82f6472a88e2dc19b089ea22 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/InternalClass_Table.java @@ -0,0 +1,132 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.InternalClass; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class InternalClass_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(InternalClass.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public InternalClass_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return InternalClass.class; + } + + @Override + public final String getName() { + return "InternalClass"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, InternalClass model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, InternalClass model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getId() != null) { + statement.bindString(2, model.getId()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, InternalClass model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO InternalClass(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO InternalClass(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE InternalClass SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM InternalClass WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS InternalClass(id TEXT, PRIMARY KEY(id))"; + } + + @Override + public final InternalClass loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + InternalClass model = new InternalClass(""); + model.setId(cursor.getStringOrDefault("id", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(InternalClass model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Location2_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/Location2_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..12d973bf73a00940faf52dc3ca086afb8805f7cb --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Location2_QueryTable.java @@ -0,0 +1,65 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.ForeignKeyModels.Location2; +import com.dbflow5.models.ForeignKeyModels.DoubleToDouble; +import java.lang.Class; +import java.lang.Double; +import java.lang.Override; + +public final class Location2_QueryTable extends RetrievalAdapter { + public static final TypeConvertedProperty latitude = new TypeConvertedProperty(Location2.class, "latitude", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Location2_QueryTable adapter = (Location2_QueryTable) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterDoubleConverter; + } + }); + + public static final Property longitude = new Property(Location2.class, "longitude"); + + private final ForeignKeyModels.DoubleConverter global_typeConverterDoubleConverter; + + public Location2_QueryTable(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDoubleConverter = (ForeignKeyModels.DoubleConverter) holder.getTypeConverterForClass(DoubleToDouble.class); + } + + @Override + public final Class table() { + return Location2.class; + } + + @Override + public final Location2 loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Location2 model = new Location2(new DoubleToDouble(0.0), 0.0); + int index_latitude = cursor.getColumnIndexForName("latitude"); + if (index_latitude != -1 && !cursor.isColumnNull(index_latitude)) { + model.setLatitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_latitude))); + } else { + model.setLatitude(global_typeConverterDoubleConverter.getModelValue(null)); + } + model.setLongitude(cursor.getDoubleOrDefault("longitude", 0.0)); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Location2 model) { + OperatorGroup clause = OperatorGroup.clause(); + Double reflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLatitude()); + clause.and(latitude.invertProperty().eq(reflatitude)); + clause.and(longitude.eq(model.getLongitude())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Location_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/Location_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..7e3178df00c0cedb72c8696fc8d8693bbec537bf --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Location_QueryTable.java @@ -0,0 +1,77 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.ForeignKeyModels.Location; +import com.dbflow5.models.ForeignKeyModels.DoubleToDouble; +import java.lang.Class; +import java.lang.Double; +import java.lang.Override; + +public final class Location_QueryTable extends RetrievalAdapter { + public static final TypeConvertedProperty latitude = new TypeConvertedProperty(Location.class, "latitude", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Location_QueryTable adapter = (Location_QueryTable) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterDoubleConverter; + } + }); + + public static final TypeConvertedProperty longitude = new TypeConvertedProperty(Location.class, "longitude", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Location_QueryTable adapter = (Location_QueryTable) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterDoubleConverter; + } + }); + + private final ForeignKeyModels.DoubleConverter global_typeConverterDoubleConverter; + + public Location_QueryTable(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDoubleConverter = (ForeignKeyModels.DoubleConverter) holder.getTypeConverterForClass(DoubleToDouble.class); + } + + @Override + public final Class table() { + return Location.class; + } + + @Override + public final Location loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Location model = new Location(new DoubleToDouble(0.0), new DoubleToDouble(0.0)); + int index_latitude = cursor.getColumnIndexForName("latitude"); + if (index_latitude != -1 && !cursor.isColumnNull(index_latitude)) { + model.setLatitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_latitude))); + } else { + model.setLatitude(global_typeConverterDoubleConverter.getModelValue(null)); + } + int index_longitude = cursor.getColumnIndexForName("longitude"); + if (index_longitude != -1 && !cursor.isColumnNull(index_longitude)) { + model.setLongitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_longitude))); + } else { + model.setLongitude(global_typeConverterDoubleConverter.getModelValue(null)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Location model) { + OperatorGroup clause = OperatorGroup.clause(); + Double reflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLatitude()); + clause.and(latitude.invertProperty().eq(reflatitude)); + Double reflongitude = global_typeConverterDoubleConverter.getDBValue(model.getLongitude()); + clause.and(longitude.invertProperty().eq(reflongitude)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ManyToMany.java b/entry/src/ohosTest/java/com/dbflow5/models/ManyToMany.java new file mode 100644 index 0000000000000000000000000000000000000000..92a53198e71a3806ca2c517ea69701977b43aabe --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ManyToMany.java @@ -0,0 +1,68 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +public class ManyToMany { + + @com.dbflow5.annotation.ManyToMany(referencedTable = Song.class) + @Table(database = TestDatabase.class) + public static class Artist{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Artist(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class Song{ + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Song(int id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ManyToManyTest.java b/entry/src/ohosTest/java/com/dbflow5/models/ManyToManyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5276b82e01a72a800cd5f783f15f31dbd865a8e1 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ManyToManyTest.java @@ -0,0 +1,33 @@ +package com.dbflow5.models; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Assert; +import org.junit.Test; + +import java.util.function.Function; + +public class ManyToManyTest extends BaseUnitTest { + @Test + private void testCanCreateManyToMany() { + FlowManager.databaseForTable(ManyToMany.Artist.class, new Function() { + @Override + public Void apply(DBFlowDatabase db) { + ManyToMany.Artist artist = new ManyToMany.Artist(0, "Andrew Grosner"); + ManyToMany.Song song = new ManyToMany.Song(0, "Livin' on A Prayer"); + + Model.save(ManyToMany.Artist.class, artist, db); + Model.save(ManyToMany.Song.class, song, db); + + Artist_Song artistSong = new Artist_Song(); + artistSong.artist = artist; + artistSong.song = song; + Assert.assertTrue(Model.save(Artist_Song.class, artistSong, db)); + return null; + } + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ModelViewTest.java b/entry/src/ohosTest/java/com/dbflow5/models/ModelViewTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5e27851eed1488de99634b02d10c508c070925bf --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ModelViewTest.java @@ -0,0 +1,18 @@ +package com.dbflow5.models; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.TestExtensions; +import com.dbflow5.models.java.JavaModelView; +import org.junit.Test; + +public class ModelViewTest extends BaseUnitTest { + @Test + private void validateModelViewQuery() { + TestExtensions.assertEquals("SELECT `id` AS `authorId`,`first_name` || ' ' || `last_name` AS `authorName` FROM `Author`", ModelViews.AuthorView.getQuery()); + } + + @Test + private void validateJavaModelViewQuery() { + TestExtensions.assertEquals("SELECT `first_name` AS `firstName`,`id` AS `id`", JavaModelView.getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ModelViews.java b/entry/src/ohosTest/java/com/dbflow5/models/ModelViews.java new file mode 100644 index 0000000000000000000000000000000000000000..f17c080d0b10bd95371e62eb4380db26b088fe39 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ModelViews.java @@ -0,0 +1,109 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ColumnMap; +import com.dbflow5.annotation.ModelView; +import com.dbflow5.annotation.ModelViewQuery; +import com.dbflow5.query.From; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.PropertyFactory; + +public class ModelViews { + + public static class AuthorName{ + public String name; + public int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AuthorName(String name, int age) { + this.name = name; + this.age = age; + } + } + + @ModelView(database = TestDatabase.class) + public static class AuthorView{ + @Column + public int authorId; + @Column + public String authorName; + @ColumnMap + public AuthorName author; + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public AuthorName getAuthor() { + return author; + } + + public void setAuthor(AuthorName author) { + this.author = author; + } + + public AuthorView(int authorId, String authorName, AuthorName author) { + this.authorId = authorId; + this.authorName = authorName; + this.author = author; + } + + @ModelViewQuery + public static From getQuery() { + return SQLite.select(Author_Table.id.as("authorId"), Author_Table.first_name.concatenate((IProperty)PropertyFactory.property(" ")) + .concatenate((IProperty)Author_Table.last_name).as("authorName")).from(ForeignKeyModels.Author.class); + } + } + + @ModelView(database = TestDatabase.class, priority = 2, allFields = true) + public static class PriorityView { + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public PriorityView(String name) { + this.name = name; + } + + public static From getQuery() { + return SQLite.select(Author_Table.first_name.plus(Author_Table.last_name).as("name")).from(ForeignKeyModels.Author.class); + } + } +} + + diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NonNullKotlinModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/NonNullKotlinModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..d5deb5b1d704b45a73e25cb08f68fafc68cb3d69 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NonNullKotlinModel_Table.java @@ -0,0 +1,174 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.DateConverter; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.NonNullKotlinModel; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; +import java.util.Date; +import javax.annotation.Generated; + +public final class NonNullKotlinModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(NonNullKotlinModel.class, "name"); + + public static final TypeConvertedProperty date = new TypeConvertedProperty(NonNullKotlinModel.class, "date", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + NonNullKotlinModel_Table adapter = (NonNullKotlinModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterDateConverter; + } + }); + + public static final Property numb = new Property(NonNullKotlinModel.class, "numb"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,date,numb}; + + private final DateConverter global_typeConverterDateConverter; + + public NonNullKotlinModel_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDateConverter = (DateConverter) holder.getTypeConverterForClass(Date.class); + } + + @Override + public final Class table() { + return NonNullKotlinModel.class; + } + + @Override + public final String getName() { + return "NonNullKotlinModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "date": { + return date; + } + case "numb": { + return numb; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, NonNullKotlinModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + Long refdate = global_typeConverterDateConverter.getDBValue(model.getDate()); + statement.bindNumberOrNull(2, refdate); + statement.bindLong(3, (long)model.getNumb()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, NonNullKotlinModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + Long refdate = global_typeConverterDateConverter.getDBValue(model.getDate()); + statement.bindNumberOrNull(2, refdate); + statement.bindLong(3, (long)model.getNumb()); + if (model.getName() != null) { + statement.bindString(4, model.getName()); + } else { + statement.bindString(4, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, NonNullKotlinModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO NonNullKotlinModel(name,date,numb) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO NonNullKotlinModel(name,date,numb) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE NonNullKotlinModel SET name=?,date=?,numb=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM NonNullKotlinModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS NonNullKotlinModel(name TEXT, date INTEGER, numb INTEGER, PRIMARY KEY(name))"; + } + + @Override + public final NonNullKotlinModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + NonNullKotlinModel model = new NonNullKotlinModel("", new Date(), 0); + model.setName(cursor.getStringOrDefault("name", "")); + int index_date = cursor.getColumnIndexForName("date"); + if (index_date != -1 && !cursor.isColumnNull(index_date)) { + model.setDate(global_typeConverterDateConverter.getModelValue(cursor.getLong(index_date))); + } + model.setNumb(cursor.getIntOrDefault("numb")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(NonNullKotlinModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.java b/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.java new file mode 100644 index 0000000000000000000000000000000000000000..b0f0edf7f347a0b3d914c0f8fef5dae188335953 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.java @@ -0,0 +1,23 @@ +package com.dbflow5.models.NonTypical; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +@Table(database = TestDatabase.class) +public class nonTypicalClassName { + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public nonTypicalClassName(int id) { + this.id = id; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..41bb31d329a31b07dc961bd2a9d80bd9cbe6dc27 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName_Table.java @@ -0,0 +1,121 @@ +package com.dbflow5.models.NonTypical; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class nonTypicalClassName_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(nonTypicalClassName.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public nonTypicalClassName_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return nonTypicalClassName.class; + } + + @Override + public final String getName() { + return "nonTypicalClassName"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, nonTypicalClassName model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, nonTypicalClassName model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, nonTypicalClassName model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO nonTypicalClassName(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO nonTypicalClassName(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE nonTypicalClassName SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM nonTypicalClassName WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS nonTypicalClassName(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final nonTypicalClassName loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + nonTypicalClassName model = new nonTypicalClassName(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(nonTypicalClassName model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NotNullReferenceModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/NotNullReferenceModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ccd5aaf4c61525ffa311945dc5e26c6c8a02d453 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NotNullReferenceModel_Table.java @@ -0,0 +1,160 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.NotNullReferenceModel; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class NotNullReferenceModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(NotNullReferenceModel.class, "name"); + + /** + * Foreign Key */ + public static final Property model_name = new Property(NotNullReferenceModel.class, "model_name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,model_name}; + + public NotNullReferenceModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return NotNullReferenceModel.class; + } + + @Override + public final String getName() { + return "NotNullReferenceModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "model_name": { + return model_name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + NotNullReferenceModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + if (model.getModel() != null) { + statement.bindStringOrNull(2, model.getModel().getName()); + } else { + statement.bindNull(2); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + NotNullReferenceModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + if (model.getModel() != null) { + statement.bindStringOrNull(2, model.getModel().getName()); + } else { + statement.bindNull(2); + } + if (model.getName() != null) { + statement.bindString(3, model.getName()); + } else { + statement.bindString(3, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + NotNullReferenceModel model) { + if (model.getName() != null) { + statement.bindString(1, model.getName()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO NotNullReferenceModel(name,model_name) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO NotNullReferenceModel(name,model_name) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE NotNullReferenceModel SET name=?,model_name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM NotNullReferenceModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS NotNullReferenceModel(name TEXT, model_name TEXT NOT NULL ON CONFLICT FAIL, PRIMARY KEY(name), FOREIGN KEY(model_name) REFERENCES SimpleModel (name) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final NotNullReferenceModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + NotNullReferenceModel model = new NotNullReferenceModel("", null); + model.setName(cursor.getStringOrDefault("name", "")); + int index_model_name_SimpleModel_Table = cursor.getColumnIndexForName("model_name"); + if (index_model_name_SimpleModel_Table != -1 && !cursor.isColumnNull(index_model_name_SimpleModel_Table)) { + model.setModel(com.dbflow5.query.SQLite.select().from(SimpleTestModels.SimpleModel.class).where() + .and(SimpleModel_Table.name.eq(cursor.getString(index_model_name_SimpleModel_Table))) + .querySingle(wrapper)); + } else { + model.setModel(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(NotNullReferenceModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NullableNumbers_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/NullableNumbers_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..48fdbf5bb5faf99867ec26e8d6009c5da9718ffe --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NullableNumbers_Table.java @@ -0,0 +1,212 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.BigDecimalConverter; +import com.dbflow5.converter.TypeConverters.BigIntegerConverter; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.NullableNumbers; +import java.lang.Class; +import java.lang.Double; +import java.lang.Float; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; +import java.math.BigDecimal; +import java.math.BigInteger; + +public final class NullableNumbers_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(NullableNumbers.class, "id"); + + public static final Property f = new Property(NullableNumbers.class, "f"); + + public static final Property d = new Property(NullableNumbers.class, "d"); + + public static final Property l = new Property(NullableNumbers.class, "l"); + + public static final Property i = new Property(NullableNumbers.class, "i"); + + public static final TypeConvertedProperty bigDecimal = new TypeConvertedProperty(NullableNumbers.class, "bigDecimal", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + NullableNumbers_Table adapter = (NullableNumbers_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterBigDecimalConverter; + } + }); + + public static final TypeConvertedProperty bigInteger = new TypeConvertedProperty(NullableNumbers.class, "bigInteger", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + NullableNumbers_Table adapter = (NullableNumbers_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterBigIntegerConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,f,d,l,i,bigDecimal,bigInteger}; + + private final BigDecimalConverter global_typeConverterBigDecimalConverter; + + private final BigIntegerConverter global_typeConverterBigIntegerConverter; + + public NullableNumbers_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterBigDecimalConverter = (BigDecimalConverter) holder.getTypeConverterForClass(BigDecimal.class); + global_typeConverterBigIntegerConverter = (BigIntegerConverter) holder.getTypeConverterForClass(BigInteger.class); + } + + @Override + public final Class table() { + return NullableNumbers.class; + } + + @Override + public final String getName() { + return "NullableNumbers"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "f": { + return f; + } + case "d": { + return d; + } + case "l": { + return l; + } + case "i": { + return i; + } + case "bigDecimal": { + return bigDecimal; + } + case "bigInteger": { + return bigInteger; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, NullableNumbers model) { + statement.bindLong(1, (long)model.getId()); + statement.bindFloatOrNull(2, model.getF()); + statement.bindDoubleOrNull(3, model.getD()); + statement.bindNumberOrNull(4, model.getL()); + statement.bindNumberOrNull(5, model.getI()); + String refbigDecimal = global_typeConverterBigDecimalConverter.getDBValue(model.getBigDecimal()); + statement.bindStringOrNull(6, refbigDecimal); + String refbigInteger = global_typeConverterBigIntegerConverter.getDBValue(model.getBigInteger()); + statement.bindStringOrNull(7, refbigInteger); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, NullableNumbers model) { + statement.bindLong(1, (long)model.getId()); + statement.bindFloatOrNull(2, model.getF()); + statement.bindDoubleOrNull(3, model.getD()); + statement.bindNumberOrNull(4, model.getL()); + statement.bindNumberOrNull(5, model.getI()); + String refbigDecimal = global_typeConverterBigDecimalConverter.getDBValue(model.getBigDecimal()); + statement.bindStringOrNull(6, refbigDecimal); + String refbigInteger = global_typeConverterBigIntegerConverter.getDBValue(model.getBigInteger()); + statement.bindStringOrNull(7, refbigInteger); + statement.bindLong(8, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, NullableNumbers model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO NullableNumbers(id,f,d,l,i,bigDecimal,bigInteger) VALUES (?,?,?,?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO NullableNumbers(id,f,d,l,i,bigDecimal,bigInteger) VALUES (?,?,?,?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE NullableNumbers SET id=?,f=?,d=?,l=?,i=?,bigDecimal=?,bigInteger=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM NullableNumbers WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS NullableNumbers(id INTEGER, f REAL, d REAL, l INTEGER, i INTEGER, bigDecimal TEXT, bigInteger TEXT, PRIMARY KEY(id))"; + } + + @Override + public final NullableNumbers loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + NullableNumbers model = new NullableNumbers(0, null, null, 0, 0, null, null); + model.setId(cursor.getIntOrDefault("id")); + model.setF(cursor.getFloatOrDefault("f", 0)); + model.setD(cursor.getDoubleOrDefault("d", 0)); + model.setL(cursor.getLongOrDefault("l", 0)); + model.setI(cursor.getIntOrDefault("i", 0)); + int index_bigDecimal = cursor.getColumnIndexForName("bigDecimal"); + if (index_bigDecimal != -1 && !cursor.isColumnNull(index_bigDecimal)) { + model.setBigDecimal(global_typeConverterBigDecimalConverter.getModelValue(cursor.getString(index_bigDecimal))); + } else { + model.setBigDecimal(global_typeConverterBigDecimalConverter.getModelValue(null)); + } + int index_bigInteger = cursor.getColumnIndexForName("bigInteger"); + if (index_bigInteger != -1 && !cursor.isColumnNull(index_bigInteger)) { + model.setBigInteger(global_typeConverterBigIntegerConverter.getModelValue(cursor.getString(index_bigInteger))); + } else { + model.setBigInteger(global_typeConverterBigIntegerConverter.getModelValue(null)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(NullableNumbers model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/NumberModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/NumberModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..c218d082ecf18d5c17a1952e33c27a8699eca214 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/NumberModel_Table.java @@ -0,0 +1,128 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.NumberModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class NumberModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(NumberModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public NumberModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return NumberModel.class; + } + + @Override + public final String getName() { + return "NumberModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final ConflictAction getUpdateOnConflictAction() { + return ConflictAction.FAIL; + } + + @Override + public final ConflictAction getInsertOnConflictAction() { + return ConflictAction.FAIL; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, NumberModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, NumberModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, NumberModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT OR FAIL INTO NumberModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO NumberModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE OR FAIL NumberModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM NumberModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS NumberModel(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final NumberModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + NumberModel model = new NumberModel(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(NumberModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/OneToManyBaseModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyBaseModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..70b2e11b1767017516d4e1fd49a50138022d2e27 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyBaseModel_Table.java @@ -0,0 +1,117 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.OneToManyModels.OneToManyBaseModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class OneToManyBaseModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(OneToManyBaseModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public OneToManyBaseModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return OneToManyBaseModel.class; + } + + @Override + public final String getName() { + return "OneToManyBaseModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, OneToManyBaseModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, OneToManyBaseModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, OneToManyBaseModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO OneToManyBaseModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO OneToManyBaseModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE OneToManyBaseModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM OneToManyBaseModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS OneToManyBaseModel(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final OneToManyBaseModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + OneToManyBaseModel model = new OneToManyBaseModel(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(OneToManyBaseModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModelTest.java b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6535d5a82f11660edab1cc32c81dca4aa031f299 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModelTest.java @@ -0,0 +1,48 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.SQLite; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class OneToManyModelTest extends BaseUnitTest { + + @Test + public void testOneToManyModel() { + FlowManager.database(TestDatabase.class, db -> { + SimpleTestModels.TwoColumnModel testModel2 = new SimpleTestModels.TwoColumnModel("Greater", 4); + Model.save(SimpleTestModels.TwoColumnModel.class, testModel2, db); + + testModel2 = new SimpleTestModels.TwoColumnModel("Lesser", 1); + Model.save(SimpleTestModels.TwoColumnModel.class, testModel2, db); + + // assert we save + OneToManyModels.OneToManyModel oneToManyModel = new OneToManyModels.OneToManyModel("HasOrders"); + Model.save(OneToManyModels.OneToManyModel.class, oneToManyModel, db); + assertTrue(Model.exists(OneToManyModels.OneToManyModel.class, oneToManyModel, db)); + + // assert loading works as expected. + oneToManyModel = SQLite.select().from(OneToManyModels.OneToManyModel.class).requireSingle(db); + assertNotNull(oneToManyModel.getRelatedOrders(db)); + assertFalse(oneToManyModel.getRelatedOrders(db).isEmpty()); + + // assert the deletion cleared the variable + Model.delete(OneToManyModels.OneToManyModel.class, oneToManyModel, db); + Assert.assertFalse(Model.exists(OneToManyModels.OneToManyModel.class, oneToManyModel, db)); + assertNull(oneToManyModel.orders); + + // assert singular relationship was deleted. + List list = SQLite.select().from(SimpleTestModels.TwoColumnModel.class).queryList(db); + assertTrue(list != null && list.size() == 1); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..b2008e3fe75524b6bde60c9a809590f4a7aa49d2 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModel_Table.java @@ -0,0 +1,310 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.OneToManyModels.OneToManyBaseModel; +import com.dbflow5.models.OneToManyModels.OneToManyModel; +import com.dbflow5.models.SimpleTestModels.TwoColumnModel; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import java.util.Collection; +import javax.annotation.Generated; + +public final class OneToManyModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(OneToManyModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public OneToManyModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return OneToManyModel.class; + } + + @Override + public final String getName() { + return "OneToManyModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, OneToManyModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, OneToManyModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, OneToManyModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO OneToManyModel(name) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO OneToManyModel(name) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE OneToManyModel SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM OneToManyModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS OneToManyModel(name TEXT, PRIMARY KEY(name))"; + } + + @Override + public final OneToManyModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + OneToManyModel model = new OneToManyModel(null); + model.setName(cursor.getStringOrDefault("name")); +// model.getSimpleModels(); +// model.getSetBaseModels(); + model.getRelatedOrders(wrapper); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(OneToManyModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } + + @Override + public final boolean delete(OneToManyModel model, DatabaseWrapper wrapper) { + boolean successful = super.delete(model, wrapper); + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.simpleModels, wrapper); + } + model.setBaseModels = null; + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.setBaseModels, wrapper); + } + model.setBaseModels = null; + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.delete(value, wrapper); + } + } + model.orders = null; + if (model.getRelatedModels(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.getRelatedModels(wrapper), wrapper); + } + model.models = null; + return successful; + } + + @Override + public final long deleteAll(Collection models, DatabaseWrapper wrapper) { + long successful = super.deleteAll(models, wrapper); + for (OneToManyModel model: models) { + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.simpleModels, wrapper); + } + model.simpleModels = null; + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.setBaseModels, wrapper); + } + model.setBaseModels = null; + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.delete(value, wrapper); + } + } + model.orders = null; + if (model.getRelatedModels(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.deleteAll(model.getRelatedModels(wrapper), wrapper); + } + model.models = null; + } + return successful; + } + + @Override + public final boolean save(OneToManyModel model, DatabaseWrapper wrapper) { + boolean successful = super.save(model, wrapper); + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.saveAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.saveAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.save(value, wrapper); + } + } + return successful; + } + + @Override + public final long saveAll(Collection models, DatabaseWrapper wrapper) { + long count = super.saveAll(models, wrapper); + for (OneToManyModel model: models) { + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.saveAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.saveAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.save(value, wrapper); + } + } + } + return count; + } + + @Override + public final long insert(OneToManyModel model, DatabaseWrapper wrapper) { + long rowId = super.insert(model, wrapper); + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.insertAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.insertAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.insert(value, wrapper); + } + } + return rowId; + } + + @Override + public final long insertAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.insertAll(models, wrapper); + for (OneToManyModel model: models) { + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.insertAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.insertAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.insert(value, wrapper); + } + } + } + return count; + } + + @Override + public final boolean update(OneToManyModel model, DatabaseWrapper wrapper) { + boolean successful = super.update(model, wrapper); + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.updateAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.updateAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.update(value, wrapper); + } + } + return successful; + } + + @Override + public final long updateAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.updateAll(models, wrapper); + for (OneToManyModel model: models) { + if (model.simpleModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.updateAll(model.simpleModels, wrapper); + } + if (model.setBaseModels != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(OneToManyBaseModel.class); + adapter.updateAll(model.setBaseModels, wrapper); + } + if (model.getRelatedOrders(wrapper) != null) { + ModelAdapter adapter = FlowManager.getModelAdapter(TwoColumnModel.class); + for (TwoColumnModel value: model.getRelatedOrders(wrapper)) { + adapter.update(value, wrapper); + } + } + } + return count; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModels.java b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModels.java new file mode 100644 index 0000000000000000000000000000000000000000..87e9d09ce0647e122ca6698966f6838a89d22140 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/OneToManyModels.java @@ -0,0 +1,83 @@ +package com.dbflow5.models; + +import com.dbflow5.annotation.OneToManyMethod; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +//import com.dbflow5.models.TwoColumnModel_Table.id; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.query.SQLite; +import com.dbflow5.structure.BaseModel; +import com.dbflow5.TestDatabase; +import com.dbflow5.structure.OneToMany; + +import java.util.List; +import java.util.function.Function; + +public class OneToManyModels { + + @Table(database = TestDatabase.class) + public static class OneToManyModel { + @PrimaryKey + public String name; + + public List orders = null; + public List models = null; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OneToManyModel(String name) { + this.name = name; + } + + @com.dbflow5.annotation.OneToMany(oneToManyMethods = {OneToManyMethod.ALL}) + public List simpleModels = new OneToMany((Function) unused -> SQLite.select().from(OneToManyBaseModel.class)).getValue(OneToManyBaseModel.class); + + @com.dbflow5.annotation.OneToMany(oneToManyMethods = {OneToManyMethod.ALL}) + public List setBaseModels = new OneToMany((Function) unused -> SQLite.select().from(OneToManyBaseModel.class)).getValue(OneToManyBaseModel.class); + + @com.dbflow5.annotation.OneToMany(oneToManyMethods = {OneToManyMethod.ALL}, variableName = "orders", efficientMethods = false) + public List getRelatedOrders(DatabaseWrapper wrapper) { + List localOrders = orders; + if (localOrders == null) { + localOrders = SQLite.select().from(SimpleTestModels.TwoColumnModel.class).where(TwoColumnModel_Table.id.greaterThan(3)).queryList(wrapper); + } + orders = localOrders; + return localOrders; + } + + @com.dbflow5.annotation.OneToMany(oneToManyMethods = {OneToManyMethod.DELETE}, variableName = "models") + public List getRelatedModels(DatabaseWrapper wrapper) { + List localModels = models; + if (localModels == null) { + localModels = SQLite.select().from(OneToManyBaseModel.class).queryList(wrapper); + } + models = localModels; + return localModels; + } + } + + @Table(database = TestDatabase.class) + public static class OneToManyBaseModel extends BaseModel { + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public OneToManyBaseModel(int id) { + this.id = id; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/OrderCursorModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/OrderCursorModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..12305f6f2fe3035e075216105b7d2fad78ff0576 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/OrderCursorModel_Table.java @@ -0,0 +1,133 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.OrderCursorModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class OrderCursorModel_Table extends ModelAdapter { + public static final Property age = new Property(OrderCursorModel.class, "age"); + + /** + * Primary Key */ + public static final Property id = new Property(OrderCursorModel.class, "id"); + + public static final Property name = new Property(OrderCursorModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{age,id,name}; + + public OrderCursorModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return OrderCursorModel.class; + } + + @Override + public final String getName() { + return "OrderCursorModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "age": { + return age; + } + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, OrderCursorModel model) { + statement.bindLong(1, (long)model.getAge()); + statement.bindLong(2, (long)model.getId()); + statement.bindStringOrNull(3, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, OrderCursorModel model) { + statement.bindLong(1, (long)model.getAge()); + statement.bindLong(2, (long)model.getId()); + statement.bindStringOrNull(3, model.getName()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, OrderCursorModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO OrderCursorModel(age,id,name) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO OrderCursorModel(age,id,name) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE OrderCursorModel SET age=?,id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM OrderCursorModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS OrderCursorModel(age INTEGER, id INTEGER, name TEXT, PRIMARY KEY(id))"; + } + + @Override + public final OrderCursorModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + OrderCursorModel model = new OrderCursorModel(0,0,""); + model.setAge(cursor.getIntOrDefault(0)); + model.setId(cursor.getIntOrDefault(1)); + model.setName(cursor.getStringOrDefault(2)); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(OrderCursorModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Outer.java b/entry/src/ohosTest/java/com/dbflow5/models/Outer.java new file mode 100644 index 0000000000000000000000000000000000000000..2723dfc27df39aa92669b044990a013f5bee83cc --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Outer.java @@ -0,0 +1,25 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; + +public class Outer { + @Table(database = TestDatabase.class) + public static class Inner{ + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Inner(int id) { + this.id = id; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Owner_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Owner_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..a535183a60e09106c01c8c8df327c47f7a14842d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Owner_Table.java @@ -0,0 +1,141 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.Owner; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Owner_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Owner.class, "id"); + + public static final Property name = new Property(Owner.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public Owner_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Owner.class; + } + + @Override + public final String getName() { + return "Owner"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Owner model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Owner model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Owner model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getName()); + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Owner model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Owner(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Owner(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Owner SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Owner WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Owner(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final Owner loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Owner model = new Owner(0, null); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final boolean exists(Owner model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Owner.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Owner model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ParentChildCachingTest.java b/entry/src/ohosTest/java/com/dbflow5/models/ParentChildCachingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..598d8a0f7d994cb30c9a04d39eec676a974e6923 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ParentChildCachingTest.java @@ -0,0 +1,33 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.SQLite; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ParentChildCachingTest extends BaseUnitTest { + @Test + public void testCanLoadChildFromCache() { + FlowManager.database(TestDatabase.class, db -> { + SimpleTestModels.TestModelChild child = new SimpleTestModels.TestModelChild(1L, "Test child"); + Model.save(SimpleTestModels.TestModelChild.class, child, db); + + SimpleTestModels.TestModelParent parent = new SimpleTestModels.TestModelParent(1, "Test parent", child); + Model.save(SimpleTestModels.TestModelParent.class, parent, db); + + parent = SQLite.select().from(SimpleTestModels.TestModelParent.class).requireSingle(db); + SimpleTestModels.TestModelChild parentChild = parent.child; + parentChild = Model.load(SimpleTestModels.TestModelChild.class, parentChild, db); + + assertEquals(1, parentChild.id); + assertEquals("Test child", parentChild.name); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Path_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Path_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..bc3f4a4e72d122ba9c1bbceef51d3d6bdd780996 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Path_Table.java @@ -0,0 +1,148 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.CachingModels.Path; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class Path_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Path.class, "id"); + + public static final Property name = new Property(Path.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public Path_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Path.class; + } + + @Override + public final String getName() { + return "Path"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Path model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Path model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getId() != null) { + statement.bindString(3, model.getId()); + } else { + statement.bindString(3, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Path model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Path(id,name) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Path(id,name) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Path SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Path WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Path(id TEXT, name TEXT, PRIMARY KEY(id))"; + } + + @Override + public final Path loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Path model = new Path("",""); + model.setId(cursor.getStringOrDefault("id", "")); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Path model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Position2_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Position2_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..881cdd3a89e63805c3c5649b2d2873675ee79102 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Position2_Table.java @@ -0,0 +1,180 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.Position2; +import com.dbflow5.models.ForeignKeyModels.DoubleToDouble; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class Position2_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Position2.class, "id"); + + /** + * Column Mapped Field */ + public static final Property latitude = new Property(Position2.class, "latitude"); + + /** + * Column Mapped Field */ + public static final Property longitude = new Property(Position2.class, "longitude"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,latitude,longitude}; + + private final ForeignKeyModels.DoubleConverter global_typeConverterDoubleConverter; + + public Position2_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDoubleConverter = (ForeignKeyModels.DoubleConverter) holder.getTypeConverterForClass(DoubleToDouble.class); + } + + @Override + public final Class table() { + return Position2.class; + } + + @Override + public final String getName() { + return "Position2"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "latitude": { + return latitude; + } + case "longitude": { + return longitude; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Position2 model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double locationreflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + if (locationreflatitude != null) { + statement.bindDouble(2, locationreflatitude); + } else { + statement.bindDouble(2, 40.6); + } + Double locationreflongitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLongitude()); + if (locationreflongitude != null) { + statement.bindDouble(3, locationreflongitude); + } else { + statement.bindDouble(3, 55.5); + } + } else { + statement.bindNull(2); + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Position2 model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double location_reflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + if (location_reflatitude != null) { + statement.bindDouble(2, location_reflatitude); + } else { + statement.bindDouble(2, 40.6); + } + Double location_reflongitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLongitude()); + if (location_reflongitude != null) { + statement.bindDouble(3, location_reflongitude); + } else { + statement.bindDouble(3, 55.5); + } + } else { + statement.bindNull(2); + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Position2 model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Position2(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Position2(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Position2 SET id=?,latitude=?,longitude=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Position2 WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Position2(id INTEGER, latitude REAL ,longitude REAL, PRIMARY KEY(id))"; + } + + @Override + public final Position2 loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Position2 model = new Position2(0, null); + model.setId(cursor.getIntOrDefault("id")); + int index_latitude_Location_QueryTable = cursor.getColumnIndexForName("latitude"); + int index_longitude_Location_QueryTable = cursor.getColumnIndexForName("longitude"); + if (index_latitude_Location_QueryTable != -1 && !cursor.isColumnNull(index_latitude_Location_QueryTable) && index_longitude_Location_QueryTable != -1 && !cursor.isColumnNull(index_longitude_Location_QueryTable)) { + model.setLocation(new ForeignKeyModels.Location(new DoubleToDouble(0.0), new DoubleToDouble(0.0))); + model.getLocation().setLatitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_latitude_Location_QueryTable))); + model.getLocation().setLongitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_longitude_Location_QueryTable))); + } else { + model.setLocation(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Position2 model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/PositionWithTypeConverter_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/PositionWithTypeConverter_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..333ed129b44bb080ff3f08a2e9178e2c9008fe9a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/PositionWithTypeConverter_Table.java @@ -0,0 +1,165 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.PositionWithTypeConverter; +import com.dbflow5.models.ForeignKeyModels.DoubleToDouble; +import com.dbflow5.models.ForeignKeyModels.DoubleConverter; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class PositionWithTypeConverter_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(PositionWithTypeConverter.class, "id"); + + /** + * Column Mapped Field */ + public static final Property latitude = new Property(PositionWithTypeConverter.class, "latitude"); + + /** + * Column Mapped Field */ + public static final Property longitude = new Property(PositionWithTypeConverter.class, "longitude"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,latitude,longitude}; + + private final DoubleConverter typeConverterDoubleConverter = new DoubleConverter(); + + public PositionWithTypeConverter_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return PositionWithTypeConverter.class; + } + + @Override + public final String getName() { + return "PositionWithTypeConverter"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "latitude": { + return latitude; + } + case "longitude": { + return longitude; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + PositionWithTypeConverter model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double locationreflatitude = typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + statement.bindDoubleOrNull(2, locationreflatitude); + statement.bindDoubleOrNull(3, model.getLocation().getLongitude()); + } else { + statement.bindNull(2); + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + PositionWithTypeConverter model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double location_reflatitude = typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + statement.bindDoubleOrNull(2, location_reflatitude); + statement.bindDoubleOrNull(3, model.getLocation().getLongitude()); + } else { + statement.bindNull(2); + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + PositionWithTypeConverter model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO PositionWithTypeConverter(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO PositionWithTypeConverter(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE PositionWithTypeConverter SET id=?,latitude=?,longitude=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM PositionWithTypeConverter WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS PositionWithTypeConverter(id INTEGER, latitude REAL ,longitude REAL, PRIMARY KEY(id))"; + } + + @Override + public final PositionWithTypeConverter loadFromCursor(FlowCursor cursor, + DatabaseWrapper wrapper) { + PositionWithTypeConverter model = new PositionWithTypeConverter(0, null); + model.setId(cursor.getIntOrDefault("id")); + int index_latitude_Location2_QueryTable = cursor.getColumnIndexForName("latitude"); + int index_longitude_Location2_QueryTable = cursor.getColumnIndexForName("longitude"); + if (index_latitude_Location2_QueryTable != -1 && !cursor.isColumnNull(index_latitude_Location2_QueryTable) && index_longitude_Location2_QueryTable != -1 && !cursor.isColumnNull(index_longitude_Location2_QueryTable)) { + model.setLocation(new ForeignKeyModels.Location2(new DoubleToDouble(0.0), 0.0)); + model.getLocation().setLatitude(typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_latitude_Location2_QueryTable))); + model.getLocation().setLongitude(cursor.getDouble(index_longitude_Location2_QueryTable)); + } else { + model.setLocation(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(PositionWithTypeConverter model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Position_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Position_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..a4eac5e3f9571c66b0689947e8882ddca6728402 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Position_Table.java @@ -0,0 +1,165 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ForeignKeyModels.Position; +import com.dbflow5.models.ForeignKeyModels.DoubleToDouble; +import com.dbflow5.models.ForeignKeyModels.DoubleConverter; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class Position_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Position.class, "id"); + + /** + * Column Mapped Field */ + public static final Property latitude = new Property(Position.class, "latitude"); + + /** + * Column Mapped Field */ + public static final Property longitude = new Property(Position.class, "longitude"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,latitude,longitude}; + + private final DoubleConverter global_typeConverterDoubleConverter; + + public Position_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterDoubleConverter = (DoubleConverter) holder.getTypeConverterForClass(DoubleToDouble.class); + } + + @Override + public final Class table() { + return Position.class; + } + + @Override + public final String getName() { + return "Position"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "latitude": { + return latitude; + } + case "longitude": { + return longitude; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Position model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double locationreflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + statement.bindDoubleOrNull(2, locationreflatitude); + Double locationreflongitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLongitude()); + statement.bindDoubleOrNull(3, locationreflongitude); + } else { + statement.bindNull(2); + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Position model) { + statement.bindLong(1, (long)model.getId()); + if (model.getLocation() != null) { + Double location_reflatitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLatitude()); + statement.bindDoubleOrNull(2, location_reflatitude); + Double location_reflongitude = global_typeConverterDoubleConverter.getDBValue(model.getLocation().getLongitude()); + statement.bindDoubleOrNull(3, location_reflongitude); + } else { + statement.bindNull(2); + statement.bindNull(3); + } + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Position model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Position(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Position(id,latitude,longitude) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Position SET id=?,latitude=?,longitude=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Position WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Position(id INTEGER, latitude REAL ,longitude REAL, PRIMARY KEY(id))"; + } + + @Override + public final Position loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Position model = new Position(0,null); + model.setId(cursor.getIntOrDefault("id")); + int index_latitude_Location_QueryTable = cursor.getColumnIndexForName("latitude"); + int index_longitude_Location_QueryTable = cursor.getColumnIndexForName("longitude"); + if (index_latitude_Location_QueryTable != -1 && !cursor.isColumnNull(index_latitude_Location_QueryTable) && index_longitude_Location_QueryTable != -1 && !cursor.isColumnNull(index_longitude_Location_QueryTable)) { + model.setLocation(new ForeignKeyModels.Location(new DoubleToDouble(0.0), new DoubleToDouble(0.0))); + model.getLocation().setLatitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_latitude_Location_QueryTable))); + model.getLocation().setLongitude(global_typeConverterDoubleConverter.getModelValue(cursor.getDouble(index_longitude_Location_QueryTable))); + } else { + model.setLocation(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Position model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/PriorityView_ViewTable.java b/entry/src/ohosTest/java/com/dbflow5/models/PriorityView_ViewTable.java new file mode 100644 index 0000000000000000000000000000000000000000..3948dca006d124123f69a42ad5456313ce7629f8 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/PriorityView_ViewTable.java @@ -0,0 +1,56 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.ModelViewAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ModelViews.PriorityView; +import java.lang.Override; +import java.lang.String; + +public final class PriorityView_ViewTable extends ModelViewAdapter { + public static final String VIEW_NAME = "PriorityView"; + + public static final Property name = new Property(PriorityView.class, "name"); + + public PriorityView_ViewTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return PriorityView.class; + } + + @Override + public final String getCreationQuery() { + return "CREATE VIEW IF NOT EXISTS PriorityView AS " + PriorityView.getQuery().getQuery(); + } + + @Override + public final String getName() { + return "PriorityView"; + } + + @Override + public final ObjectType getType() { + return ObjectType.View; + } + + @Override + public final PriorityView loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + PriorityView model = new PriorityView(""); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(PriorityView model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizEntry_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizEntry_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f1753e231590db62a6c8505b64558fd8c0a7ed --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizEntry_Table.java @@ -0,0 +1,186 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.IndexProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.WrapperProperty; +import com.dbflow5.models.ProspectQuizs.ProspectQuizEntry; +import com.dbflow5.models.ProspectQuizs.QuizParticipantStatus; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; + +public final class ProspectQuizEntry_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property profileID = new Property(ProspectQuizEntry.class, "profileID"); + + /** + * Foreign Key / Primary Key */ + public static final Property quizID_ID = new Property(ProspectQuizEntry.class, "quizID_ID"); + + public static final Property text = new Property(ProspectQuizEntry.class, "text"); + + public static final WrapperProperty participantStatus = new WrapperProperty(ProspectQuizEntry.class, "participantStatus"); + + public static final Property name = new Property(ProspectQuizEntry.class, "name"); + + public static final Property answerEpoch = new Property(ProspectQuizEntry.class, "answerEpoch"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{profileID,quizID_ID,text,participantStatus,name,answerEpoch}; + + public static final IndexProperty index_quizid_answerts = new IndexProperty<>("quizid_answerts", false, ProspectQuizEntry.class,quizID_ID, answerEpoch); + + public ProspectQuizEntry_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return ProspectQuizEntry.class; + } + + @Override + public final String getName() { + return "ProspectQuizEntry"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "profileID": { + return profileID; + } + case "quizID_ID": { + return quizID_ID; + } + case "text": { + return text; + } + case "participantStatus": { + return participantStatus; + } + case "name": { + return name; + } + case "answerEpoch": { + return answerEpoch; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, ProspectQuizEntry model) { + statement.bindStringOrNull(1, model.profileID); + statement.bindStringOrNull(2, model.quizID); + statement.bindStringOrNull(3, model.getText()); + String refparticipantStatus = model.getParticipantStatus() != null ? model.getParticipantStatus().name() : null; + statement.bindStringOrNull(4, refparticipantStatus); + if (model.getName() != null) { + statement.bindString(5, model.getName()); + } else { + statement.bindString(5, ""); + } + statement.bindLong(6, model.getAnswerEpoch()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, ProspectQuizEntry model) { + statement.bindStringOrNull(1, model.profileID); + statement.bindStringOrNull(2, model.quizID); + statement.bindStringOrNull(3, model.getText()); + String refparticipantStatus = model.getParticipantStatus() != null ? model.getParticipantStatus().name() : null; + statement.bindStringOrNull(4, refparticipantStatus); + if (model.getName() != null) { + statement.bindString(5, model.getName()); + } else { + statement.bindString(5, ""); + } + statement.bindLong(6, model.getAnswerEpoch()); + statement.bindStringOrNull(7, model.profileID); + statement.bindStringOrNull(8, model.quizID); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, ProspectQuizEntry model) { + statement.bindStringOrNull(1, model.profileID); + statement.bindStringOrNull(2, model.quizID); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO ProspectQuizEntry(profileID,quizID_ID,text,participantStatus,name,answerEpoch) VALUES (?,?,?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO ProspectQuizEntry(profileID,quizID_ID,text,participantStatus,name,answerEpoch) VALUES (?,?,?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE ProspectQuizEntry SET profileID=?,quizID_ID=?,text=?,participantStatus=?,name=?,answerEpoch=? WHERE profileID=? AND quizID_ID=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM ProspectQuizEntry WHERE profileID=? AND quizID_ID=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS ProspectQuizEntry(profileID TEXT NOT NULL ON CONFLICT FAIL, quizID_ID TEXT NOT NULL ON CONFLICT FAIL, text TEXT, participantStatus TEXT, name TEXT NOT NULL ON CONFLICT FAIL, answerEpoch INTEGER NOT NULL ON CONFLICT FAIL, PRIMARY KEY(profileID, quizID_ID), FOREIGN KEY(quizID_ID) REFERENCES ProspectQuiz (`ID`) ON UPDATE NO ACTION ON DELETE CASCADE)"; + } + + @Override + public final ProspectQuizEntry loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + ProspectQuizEntry model = new ProspectQuizEntry(); + model.profileID = cursor.getStringOrDefault("profileID"); + model.quizID = cursor.getStringOrDefault("quizID_ID"); + model.setText(cursor.getStringOrDefault("text")); + int index_participantStatus = cursor.getColumnIndexForName("participantStatus"); + if (index_participantStatus != -1 && !cursor.isColumnNull(index_participantStatus)) { + try { + model.setParticipantStatus(QuizParticipantStatus.valueOf(cursor.getString(index_participantStatus))); + } catch (IllegalArgumentException e) { + model.setParticipantStatus(null); + } + } else { + model.setParticipantStatus(null); + } + model.setName(cursor.getStringOrDefault("name", "")); + model.setAnswerEpoch(cursor.getLongOrDefault("answerEpoch")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(ProspectQuizEntry model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(profileID.eq(model.profileID)); + clause.and(quizID_ID.eq(model.quizID)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuiz_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuiz_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..aaa8b36cf0dd29efdbf281bd5b063ff749bdb5df --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuiz_Table.java @@ -0,0 +1,205 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.IndexProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.ProspectQuizs.ProspectQuiz; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; +import java.util.Set; + +public final class ProspectQuiz_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property ID = new Property(ProspectQuiz.class, "ID"); + + public static final Property question = new Property(ProspectQuiz.class, "question"); + + public static final Property pendingResponseCount = new Property(ProspectQuiz.class, "pendingResponseCount"); + + public static final Property resolvedResponseCount = new Property(ProspectQuiz.class, "resolvedResponseCount"); + + public static final Property newResponseCount = new Property(ProspectQuiz.class, "newResponseCount"); + + public static final TypeConvertedProperty> pendingUnanswered = new TypeConvertedProperty>(ProspectQuiz.class, "pendingUnanswered", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + ProspectQuiz_Table adapter = (ProspectQuiz_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.typeConverterMutableSetTypeConverter; + } + }); + + public static final Property modifiedDate = new Property(ProspectQuiz.class, "modifiedDate"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{ID,question,pendingResponseCount,resolvedResponseCount,newResponseCount,pendingUnanswered,modifiedDate}; + + public static final IndexProperty index_modified = new IndexProperty<>("modified", false, ProspectQuiz.class,modifiedDate); + + private final ProspectQuizs.MutableSetTypeConverter typeConverterMutableSetTypeConverter = new ProspectQuizs.MutableSetTypeConverter(); + + public ProspectQuiz_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return ProspectQuiz.class; + } + + @Override + public final String getName() { + return "ProspectQuiz"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "ID": { + return ID; + } + case "question": { + return question; + } + case "pendingResponseCount": { + return pendingResponseCount; + } + case "resolvedResponseCount": { + return resolvedResponseCount; + } + case "newResponseCount": { + return newResponseCount; + } + case "pendingUnanswered": { + return pendingUnanswered; + } + case "modifiedDate": { + return modifiedDate; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, ProspectQuiz model) { + statement.bindStringOrNull(1, model.ID); + if (model.getQuestion() != null) { + statement.bindString(2, model.getQuestion()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getPendingResponseCount()); + statement.bindLong(4, (long)model.getResolvedResponseCount()); + statement.bindLong(5, (long)model.getNewResponseCount()); + String refpendingUnanswered = typeConverterMutableSetTypeConverter.getDBValue(model.getPendingUnanswered()); + statement.bindStringOrNull(6, refpendingUnanswered); + if (model.getModifiedDate() >= 0) { + statement.bindNumber(7, model.getModifiedDate()); + } else { + statement.bindLong(7, 1L); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, ProspectQuiz model) { + statement.bindStringOrNull(1, model.ID); + if (model.getQuestion() != null) { + statement.bindString(2, model.getQuestion()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getPendingResponseCount()); + statement.bindLong(4, (long)model.getResolvedResponseCount()); + statement.bindLong(5, (long)model.getNewResponseCount()); + String refpendingUnanswered = typeConverterMutableSetTypeConverter.getDBValue(model.getPendingUnanswered()); + statement.bindStringOrNull(6, refpendingUnanswered); + if (model.getModifiedDate() >= 0) { + statement.bindNumber(7, model.getModifiedDate()); + } else { + statement.bindLong(7, 1L); + } + statement.bindStringOrNull(8, model.ID); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, ProspectQuiz model) { + statement.bindStringOrNull(1, model.ID); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO ProspectQuiz(ID,question,pendingResponseCount,resolvedResponseCount,newResponseCount,pendingUnanswered,modifiedDate) VALUES (?,?,?,?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO ProspectQuiz(ID,question,pendingResponseCount,resolvedResponseCount,newResponseCount,pendingUnanswered,modifiedDate) VALUES (?,?,?,?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE ProspectQuiz SET ID=?,question=?,pendingResponseCount=?,resolvedResponseCount=?,newResponseCount=?,pendingUnanswered=?,modifiedDate=? WHERE ID=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM ProspectQuiz WHERE ID=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS ProspectQuiz(ID TEXT NOT NULL ON CONFLICT FAIL, question TEXT NOT NULL ON CONFLICT FAIL, pendingResponseCount INTEGER NOT NULL ON CONFLICT FAIL, resolvedResponseCount INTEGER NOT NULL ON CONFLICT FAIL, newResponseCount INTEGER NOT NULL ON CONFLICT FAIL, pendingUnanswered TEXT NOT NULL ON CONFLICT FAIL, modifiedDate INTEGER, PRIMARY KEY(ID))"; + } + + @Override + public final ProspectQuiz loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + ProspectQuiz model = new ProspectQuiz(); + model.ID = cursor.getStringOrDefault("ID"); + model.setQuestion(cursor.getStringOrDefault("question", "")); + model.setPendingResponseCount(cursor.getIntOrDefault("pendingResponseCount")); + model.setResolvedResponseCount(cursor.getIntOrDefault("resolvedResponseCount")); + model.setNewResponseCount(cursor.getIntOrDefault("newResponseCount")); + int index_pendingUnanswered = cursor.getColumnIndexForName("pendingUnanswered"); + if (index_pendingUnanswered != -1 && !cursor.isColumnNull(index_pendingUnanswered)) { + model.setPendingUnanswered(typeConverterMutableSetTypeConverter.getModelValue(cursor.getString(index_pendingUnanswered))); + } + model.setModifiedDate(cursor.getLongOrDefault("modifiedDate", 1L)); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(ProspectQuiz model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(ID.eq(model.ID)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizs.java b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizs.java new file mode 100644 index 0000000000000000000000000000000000000000..3854f805ff6f4d6263f40db83437244523b5ff25 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/ProspectQuizs.java @@ -0,0 +1,259 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.TypeConverter; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.ForeignKeyAction; +import com.dbflow5.annotation.Index; +import com.dbflow5.annotation.IndexGroup; +import com.dbflow5.annotation.NotNull; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.structure.BaseModel; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ProspectQuizs { + + @TypeConverter + public static class MutableSetTypeConverter extends TypeConverters.TypeConverter> { + @Override + public String getDBValue(Set model) { + if (model != null) { + return StringUtils.joinToString(model, ", "); + } + return null; + } + + @Override + public Set getModelValue(String data) { + if (data != null) { + List list = Arrays.asList(data.split("")); + return new HashSet<>(list); + } + return null; + } + } + + + /* + * A quiz, consisting primarily of a question from the current user, and a set of responses from + * prospects and/or prior prospects that the user has acted upon. Though the author ID is needed on the server, the + * client has no need of it. Instead, just the canonical quiz identifier is used. + * It is envisioned that the resolvedResponses may be provided only when an additional parameter is + * specified in the request. + */ + @Table(database = TestDatabase.class, allFields = true, useBooleanGetterSetters = false, indexGroups = {@IndexGroup(number = 1, name = "modified")}) + public static class ProspectQuiz extends BaseModel { + @NotNull + @PrimaryKey + public String ID; + + @NotNull + public String question; + + @NotNull + public int pendingResponseCount; + + @NotNull + public int resolvedResponseCount; + + @NotNull + public int newResponseCount; + + // We have a list of prospect IDs that have been invited but not answered. Just IDs to be used + // with determining who can still be added. + @NotNull + @Column(typeConverter = MutableSetTypeConverter.class) + public Set pendingUnanswered; + + @Index(indexGroups = {1}) + @Column(defaultValue = "1L") + public long modifiedDate; + + public String getID() { + return ID; + } + + public void setID(String ID) { + this.ID = ID; + } + + public String getQuestion() { + return question; + } + + public void setQuestion(String question) { + this.question = question; + } + + public int getPendingResponseCount() { + return pendingResponseCount; + } + + public void setPendingResponseCount(int pendingResponseCount) { + this.pendingResponseCount = pendingResponseCount; + } + + public int getResolvedResponseCount() { + return resolvedResponseCount; + } + + public void setResolvedResponseCount(int resolvedResponseCount) { + this.resolvedResponseCount = resolvedResponseCount; + } + + public int getNewResponseCount() { + return newResponseCount; + } + + public void setNewResponseCount(int newResponseCount) { + this.newResponseCount = newResponseCount; + } + + public Set getPendingUnanswered() { + return pendingUnanswered; + } + + public void setPendingUnanswered(Set pendingUnanswered) { + this.pendingUnanswered = pendingUnanswered; + } + + public long getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(long modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public ProspectQuiz(String quizID) { + this(); + ID = quizID; + } + + public ProspectQuiz() { + question = ""; + pendingResponseCount = 0; + resolvedResponseCount = 0; + newResponseCount = 0; + pendingUnanswered = new HashSet<>(); + } + } + + /** + * An element of a quiz, consisting of a user the quiz targeted and his/her response + * Status is only used on the quiz full view, in which profiles that have been rejected or matched + * will be included in the quiz, but shown separately. In the quiz full view, a participant whose + * account is deactivated or deleted should never be visible. When a quiz is refreshed and a participant + * deleted or deactivated the account, the entry for the deleted user will disappear from the response. + */ + @Table(database = TestDatabase.class, allFields = true, useBooleanGetterSetters = false, indexGroups = {@IndexGroup(number = 1, name = "quizid_answerts")}) + public static class ProspectQuizEntry extends BaseModel { + @PrimaryKey + @NotNull + public String profileID; + + @PrimaryKey + @NotNull + @Index(indexGroups = {1}) + @ForeignKey(stubbedRelationship = true, + tableClass = ProspectQuiz.class, + onDelete = ForeignKeyAction.CASCADE) + public String quizID; + + //@ForeignKey(saveForeignKeyModel = true) var photo: PhotoMedia? + public String text; + public QuizParticipantStatus participantStatus; + + @NotNull + public String name; + + @NotNull + @Index(indexGroups = {1}) + public long answerEpoch; + + public String getProfileID() { + return profileID; + } + + public void setProfileID(String profileID) { + this.profileID = profileID; + } + + public String getQuizID() { + return quizID; + } + + public void setQuizID(String quizID) { + this.quizID = quizID; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public QuizParticipantStatus getParticipantStatus() { + return participantStatus; + } + + public void setParticipantStatus(QuizParticipantStatus participantStatus) { + this.participantStatus = participantStatus; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getAnswerEpoch() { + return answerEpoch; + } + + public void setAnswerEpoch(long answerEpoch) { + this.answerEpoch = answerEpoch; + } + + public ProspectQuizEntry() { + //photo = null + text = null; + participantStatus = null; + name = ""; + //new = false + answerEpoch = 0; + } + } + + enum QuizParticipantStatus { + Pending, + Rejected, + Connected, + ; + + public static QuizParticipantStatus fromCode(int code) { + switch (code) { + case 0: + return Pending; + case 1: + return Rejected; + case 2: + return Connected; + default: + throw new IllegalArgumentException("Invalid raw int for QuizParticipantStatus"); + } + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/QueryModelTest.java b/entry/src/ohosTest/java/com/dbflow5/models/QueryModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7cfd05794cc7fb0cdad1962a20e42d0112a4798c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/QueryModelTest.java @@ -0,0 +1,36 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowManager; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.SQLite; +import com.dbflow5.structure.Model; +import org.junit.Assert; +import org.junit.Test; + +public class QueryModelTest extends BaseUnitTest { + @Test + public void testCanLoadAuthorBlogs() { + FlowManager.database(TestDatabase.class, db -> { + ForeignKeyModels.Author author = new ForeignKeyModels.Author(0, "Andrew", "Grosner"); + Model.save(ForeignKeyModels.Author.class, author, db); + ForeignKeyModels.Blog blog = new ForeignKeyModels.Blog(0, "My First Blog", author); + Model.save(ForeignKeyModels.Blog.class, blog, db); + + assert(Model.exists(ForeignKeyModels.Author.class, author, db)); + assert(Model.exists(ForeignKeyModels.Blog.class, blog, db)); + + QueryModels.AuthorNameQuery result = SQLite.select(Blog_Table.name.withTable().as("blogName"), Author_Table.id.withTable().as("authorId"), + Blog_Table.id.withTable().as("blogId")) + .from(ForeignKeyModels.Blog.class) + .innerJoin(ForeignKeyModels.Author.class) + .on(Blog_Table.author_id.withTable().eq (Author_Table.id.withTable())) + .queryCustomSingle(QueryModels.AuthorNameQuery.class, db); + + Assert.assertEquals(blog.id, result.blogId); + return null; + }); + + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/QueryModels.java b/entry/src/ohosTest/java/com/dbflow5/models/QueryModels.java new file mode 100644 index 0000000000000000000000000000000000000000..4f153b1ade38b22725038ec151395f97fee2792c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/QueryModels.java @@ -0,0 +1,115 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.QueryModel; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.data.Blob; + +public class QueryModels{ + + @QueryModel(database = TestDatabase.class, allFields = true) + public static class AuthorNameQuery{ + public String blogName; + public int authorId; + public int blogId; + + public String getBlogName() { + return blogName; + } + + public void setBlogName(String blogName) { + this.blogName = blogName; + } + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public int getBlogId() { + return blogId; + } + + public void setBlogId(int blogId) { + this.blogId = blogId; + } + + public AuthorNameQuery(String blogName, int authorId, int blogId) { + this.blogName = blogName; + this.authorId = authorId; + this.blogId = blogId; + } + } + + @QueryModel(database = TestDatabase.class) + public static class CustomBlobModel{ + @Column + public MyBlob myBlob; + + public MyBlob getMyBlob() { + return myBlob; + } + + public void setMyBlob(MyBlob myBlog) { + this.myBlob = myBlog; + } + + public CustomBlobModel(MyBlob myBlob) { + this.myBlob = myBlob; + } + + public static class MyBlob{ + public byte[] blob; + + public MyBlob(byte[] blob) { + this.blob = blob; + } + } + + @com.dbflow5.annotation.TypeConverter + public static class MyTypeConverter extends TypeConverters.TypeConverter { + + public MyTypeConverter() { + super(); + } + + @Override + public Blob getDBValue(MyBlob model) { + if(model != null) { + return new Blob(model.blob); + } + return null; + } + + @Override + public MyBlob getModelValue(Blob data) { + if(data != null) { + return new MyBlob(data.getBlob()); + } + return null; + } + } + } + + @QueryModel(database = TestDatabase.class, allFields = true) + public static class AllFieldsQueryModel{ + public String fieldModel; + + public String getFieldModel() { + return fieldModel; + } + + public void setFieldModel(String fieldModel) { + this.fieldModel = fieldModel; + } + + public AllFieldsQueryModel(String fieldModel) { + this.fieldModel = fieldModel; + } + } +} + diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Refund.java b/entry/src/ohosTest/java/com/dbflow5/models/Refund.java new file mode 100644 index 0000000000000000000000000000000000000000..283d33229f69592744ac9cc7ae4f19b440a02d84 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Refund.java @@ -0,0 +1,51 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.structure.BaseModel; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +@Table( + database = TestDatabase.class +) +public final class Refund extends BaseModel { + @PrimaryKey( + autoincrement = true + ) + long _id; + + @ForeignKey( + saveForeignKeyModel = true + ) + SimpleTestModels.Transfer refund_in; + + @ForeignKey( + saveForeignKeyModel = true + ) + SimpleTestModels.Transfer refund_out; + + public final long getId() { + return _id; + } + + public final SimpleTestModels.Transfer getRefund_in() { + return refund_in; + } + + public final void setRefund_in(SimpleTestModels.Transfer param) { + refund_in = param; + } + + public final SimpleTestModels.Transfer getRefund_out() { + return refund_out; + } + + public final void setRefund_out(SimpleTestModels.Transfer param) { + refund_out = param; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Refund_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Refund_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..23200a4630b8d6159ec72137d13240ef5767e40c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Refund_Table.java @@ -0,0 +1,204 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.UUIDConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; +import java.util.UUID; + +public final class Refund_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property _id = new Property(Refund.class, "_id"); + + /** + * Foreign Key */ + public static final Property refund_in_transfer_id = new Property(Refund.class, "refund_in_transfer_id"); + + /** + * Foreign Key */ + public static final Property refund_out_transfer_id = new Property(Refund.class, "refund_out_transfer_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{_id,refund_in_transfer_id,refund_out_transfer_id}; + + private final UUIDConverter global_typeConverterUUIDConverter; + + public Refund_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterUUIDConverter = (UUIDConverter) holder.getTypeConverterForClass(UUID.class); + } + + @Override + public final Class table() { + return Refund.class; + } + + @Override + public final String getName() { + return "Refund"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "_id": { + return _id; + } + case "refund_in_transfer_id": { + return refund_in_transfer_id; + } + case "refund_out_transfer_id": { + return refund_out_transfer_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Refund model, Number id) { + model._id = id.longValue(); + } + + @Override + public final void saveForeignKeys(Refund model, DatabaseWrapper wrapper) { + if (model.refund_in != null) { + FlowManager.getModelAdapter(SimpleTestModels.Transfer.class).save(model.refund_in, wrapper); + } + if (model.refund_out != null) { + FlowManager.getModelAdapter(SimpleTestModels.Transfer.class).save(model.refund_out, wrapper); + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Refund model) { + statement.bindLong(1, model._id); + if (model.refund_in != null) { + String refund_inreftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.refund_in.getTransfer_id()); + statement.bindStringOrNull(2, refund_inreftransfer_id); + } else { + statement.bindNull(2); + } + if (model.refund_out != null) { + String refund_outreftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.refund_out.getTransfer_id()); + statement.bindStringOrNull(3, refund_outreftransfer_id); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Refund model) { + statement.bindLong(1, model._id); + if (model.refund_in != null) { + String refund_in_reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.refund_in.getTransfer_id()); + statement.bindStringOrNull(2, refund_in_reftransfer_id); + } else { + statement.bindNull(2); + } + if (model.refund_out != null) { + String refund_out_reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.refund_out.getTransfer_id()); + statement.bindStringOrNull(3, refund_out_reftransfer_id); + } else { + statement.bindNull(3); + } + statement.bindLong(4, model._id); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Refund model) { + statement.bindLong(1, model._id); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Refund(_id,refund_in_transfer_id,refund_out_transfer_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Refund(_id,refund_in_transfer_id,refund_out_transfer_id) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Refund SET _id=?,refund_in_transfer_id=?,refund_out_transfer_id=? WHERE _id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Refund WHERE _id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Refund(_id INTEGER PRIMARY KEY AUTOINCREMENT, refund_in_transfer_id TEXT, refund_out_transfer_id TEXT, FOREIGN KEY(refund_in_transfer_id) REFERENCES Transfer (`transfer_id`) ON UPDATE NO ACTION ON DELETE NO ACTION, FOREIGN KEY(refund_out_transfer_id) REFERENCES Transfer (`transfer_id`) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final Refund loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Refund model = new Refund(); + model._id = cursor.getLongOrDefault("_id"); + int index_refund_in_transfer_id_Transfer_Table = cursor.getColumnIndexForName("refund_in_transfer_id"); + if (index_refund_in_transfer_id_Transfer_Table != -1 && !cursor.isColumnNull(index_refund_in_transfer_id_Transfer_Table)) { + model.refund_in = SQLite.select().from(SimpleTestModels.Transfer.class).where() + .and(Transfer_Table.transfer_id.eq(global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_refund_in_transfer_id_Transfer_Table)))) + .querySingle(wrapper); + } else { + model.refund_in = null; + } + int index_refund_out_transfer_id_Transfer_Table = cursor.getColumnIndexForName("refund_out_transfer_id"); + if (index_refund_out_transfer_id_Transfer_Table != -1 && !cursor.isColumnNull(index_refund_out_transfer_id_Transfer_Table)) { + model.refund_out = SQLite.select().from(SimpleTestModels.Transfer.class).where() + .and(Transfer_Table.transfer_id.eq(global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_refund_out_transfer_id_Transfer_Table)))) + .querySingle(wrapper); + } else { + model.refund_out = null; + } + return model; + } + + @Override + public final boolean exists(Refund model, DatabaseWrapper wrapper) { + return model._id > 0 + && SQLite.selectCountOf() + .from(Refund.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Refund model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(_id.eq(model._id)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleCacheObject_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleCacheObject_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..203c0ed41bfc2797cfcf944af02c58e83a575fab --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleCacheObject_Table.java @@ -0,0 +1,259 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleKeyCacheableListModelLoader; +import com.dbflow5.adapter.queriable.SingleKeyCacheableModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.CacheableListModelSaver; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.cache.SimpleMapCache; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.CachingModels.SimpleCacheObject; +import java.lang.IllegalArgumentException; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Collection; + +public final class SimpleCacheObject_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(SimpleCacheObject.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public static final CacheAdapter cacheAdapter = new CacheAdapter(new SimpleMapCache(25), 1, null) { + @Override + public final Object getCachingColumnValueFromModel(SimpleCacheObject model) { + return model.getId(); + } + + @Override + public final Object getCachingColumnValueFromCursor(FlowCursor cursor) { + return cursor.getString(cursor.getColumnIndexForName("id")); + } + + @Override + public final Object getCachingId(SimpleCacheObject model) { + return getCachingColumnValueFromModel(model); + } + + @Override + public Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor) { + return new Object[0]; + } + + @Override + public Object[] getCachingColumnValuesFromModel(Object[] inValues, SimpleCacheObject TModel) { + return new Object[0]; + } + + @Override + public void reloadRelationships(SimpleCacheObject model, FlowCursor cursor, DatabaseWrapper databaseWrapper) { + } + }; + + public SimpleCacheObject_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SimpleCacheObject.class; + } + + @Override + public final String getName() { + return "SimpleCacheObject"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final SingleModelLoader createSingleModelLoader() { + return new SingleKeyCacheableModelLoader(table(), cacheAdapter); + } + + @Override + public final ListModelLoader createListModelLoader() { + return new SingleKeyCacheableListModelLoader<>(table(), cacheAdapter); + } + + @Override + protected CacheableListModelSaver createListModelSaver() { + return new CacheableListModelSaver<>(getModelSaver(), cacheAdapter); + } + + @Override + public final boolean cachingEnabled() { + return true; + } + + @Override + public final SimpleCacheObject load(SimpleCacheObject model, DatabaseWrapper wrapper) { + SimpleCacheObject loaded = super.load(model, wrapper); + cacheAdapter.storeModelInCache(model); + return loaded; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, SimpleCacheObject model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, SimpleCacheObject model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getId() != null) { + statement.bindString(2, model.getId()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, SimpleCacheObject model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleCacheObject(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleCacheObject(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleCacheObject SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleCacheObject WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleCacheObject(id TEXT, PRIMARY KEY(id))"; + } + + @Override + public final SimpleCacheObject loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SimpleCacheObject model = new SimpleCacheObject(""); + model.setId(cursor.getStringOrDefault("id", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SimpleCacheObject model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } + + @Override + public final boolean delete(SimpleCacheObject model, DatabaseWrapper wrapper) { + cacheAdapter.removeModelFromCache(model); + boolean successful = super.delete(model, wrapper); + return successful; + } + + @Override + public final long deleteAll(Collection models, + DatabaseWrapper wrapper) { + cacheAdapter.removeModelsFromCache(models); + long successful = super.deleteAll(models, wrapper); + return successful; + } + + @Override + public final boolean save(SimpleCacheObject model, DatabaseWrapper wrapper) { + boolean successful = super.save(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long saveAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.saveAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final long insert(SimpleCacheObject model, DatabaseWrapper wrapper) { + long rowId = super.insert(model, wrapper); + cacheAdapter.storeModelInCache(model); + return rowId; + } + + @Override + public final long insertAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.insertAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final boolean update(SimpleCacheObject model, DatabaseWrapper wrapper) { + boolean successful = super.update(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long updateAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.updateAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleCustomModel_QueryTable.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleCustomModel_QueryTable.java new file mode 100644 index 0000000000000000000000000000000000000000..6a7872fedece79ed671e3e6ff6ccab906ac1d548 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleCustomModel_QueryTable.java @@ -0,0 +1,38 @@ +package com.dbflow5.models; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.SimpleCustomModel; +import java.lang.Override; +import java.lang.String; + +public final class SimpleCustomModel_QueryTable extends RetrievalAdapter { + public static final Property name = new Property(SimpleCustomModel.class, "name"); + + public SimpleCustomModel_QueryTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SimpleCustomModel.class; + } + + @Override + public final SimpleCustomModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SimpleCustomModel model = new SimpleCustomModel(""); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SimpleCustomModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..217cc4ed4784d24ca7a9894c3a64ace4347ebd07 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleModel_Table.java @@ -0,0 +1,117 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +public final class SimpleModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(SimpleModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public SimpleModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SimpleModel.class; + } + + @Override + public final String getName() { + return "SimpleModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, SimpleModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, SimpleModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, SimpleModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleModel(name) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleModel(name) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleModel SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleModel(name TEXT, PRIMARY KEY(name))"; + } + + @Override + public final SimpleModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SimpleModel model = new SimpleModel(""); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SimpleModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleQuickCheckModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleQuickCheckModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..00a87a3ba548741f4da8f09bd86e4573d205e009 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleQuickCheckModel_Table.java @@ -0,0 +1,131 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.SimpleQuickCheckModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class SimpleQuickCheckModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property name = new Property(SimpleQuickCheckModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name}; + + public SimpleQuickCheckModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SimpleQuickCheckModel.class; + } + + @Override + public final String getName() { + return "SimpleQuickCheckModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(SimpleQuickCheckModel model, Number id) { + model.setName(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, + SimpleQuickCheckModel model) { + statement.bindLong(1, (long)model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, + SimpleQuickCheckModel model) { + statement.bindLong(1, (long)model.getName()); + statement.bindLong(2, (long)model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, + SimpleQuickCheckModel model) { + statement.bindLong(1, (long)model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleQuickCheckModel(name) VALUES (nullif(?, 0))"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleQuickCheckModel(name) VALUES (nullif(?, 0))"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleQuickCheckModel SET name=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleQuickCheckModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleQuickCheckModel(name INTEGER PRIMARY KEY AUTOINCREMENT)"; + } + + @Override + public final SimpleQuickCheckModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SimpleQuickCheckModel model = new SimpleQuickCheckModel(0); + model.setName(cursor.getIntOrDefault("name")); + return model; + } + + @Override + public final boolean exists(SimpleQuickCheckModel model, DatabaseWrapper wrapper) { + return model.getName() > 0; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SimpleQuickCheckModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModels.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModels.java new file mode 100644 index 0000000000000000000000000000000000000000..37292c04a3e9935018d06080fa34bf4a083a36ef --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModels.java @@ -0,0 +1,1224 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.*; +import com.dbflow5.annotation.ManyToMany; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.data.Blob; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.query.SQLiteStatementListener; +import com.dbflow5.structure.BaseModel; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Date; +import java.util.UUID; + +public class SimpleTestModels { + @Table(database = TestDatabase.class) + public static class SimpleModel { + @PrimaryKey + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SimpleModel(String name) { + this.name = name; + } + } + + @QueryModel(database = TestDatabase.class) + public static class SimpleCustomModel { + @Column + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SimpleCustomModel(String name) { + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class SimpleQuickCheckModel { + @PrimaryKey(quickCheckAutoIncrement = true, autoincrement = true) + public int name; + + public int getName() { + return name; + } + + public void setName(int name) { + this.name = name; + } + + public SimpleQuickCheckModel(int name) { + this.name = name; + } + } + + @Table(database = TestDatabase.class, insertConflict = ConflictAction.FAIL, updateConflict = ConflictAction.FAIL) + public static class NumberModel { + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public NumberModel(int id) { + this.id = id; + } + } + + @Table(database = TestDatabase.class) + public static class CharModel { + @PrimaryKey + public int id; + @Column + public Character exampleChar; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Character getExampleChar() { + return exampleChar; + } + + public void setExampleChar(Character exampleChar) { + this.exampleChar = exampleChar; + } + + public CharModel(int id, Character exampleChar) { + this.id = id; + this.exampleChar = exampleChar; + } + } + + @Table(database = TestDatabase.class) + public static class TwoColumnModel { + @PrimaryKey + public String name; + @Column(defaultValue = "56") + public int id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public TwoColumnModel(String name, int id) { + this.name = name; + this.id = id; + } + } + + @Table(database = TestDatabase.class, createWithDatabase = false) + public static class DontCreateModel { + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public DontCreateModel(int id) { + this.id = id; + } + } + + public enum Difficulty { + EASY, + MEDIUM, + HARD + } + + @Table(database = TestDatabase.class) + public static class EnumModel { + @PrimaryKey + public int id; + @Column + public Difficulty difficulty; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Difficulty getDifficulty() { + return difficulty; + } + + public void setDifficulty(Difficulty difficulty) { + this.difficulty = difficulty; + } + + public EnumModel(int id, Difficulty difficulty) { + this.id = id; + this.difficulty = difficulty; + } + } + + @Table(database = TestDatabase.class, allFields = true) + public static class AllFieldsModel { + @PrimaryKey + public String name; + public int count; + @Column(getterName = "getTruth") + public boolean truth; + public String fileName; + @ColumnIgnore + public int hidden; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public boolean isTruth() { + return truth; + } + + public void setTruth(boolean truth) { + this.truth = truth; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public int getHidden() { + return hidden; + } + + public void setHidden(int hidden) { + this.hidden = hidden; + } + + public static int getCOUNT() { + return COUNT; + } + + public AllFieldsModel(String name, int count, boolean truth, String fileName, int hidden) { + this.name = name; + this.count = count; + this.truth = truth; + this.fileName = fileName; + this.hidden = hidden; + } + + public static final int COUNT = 0; + } + + @Table(database = TestDatabase.class, allFields = true) + public static class SubclassAllFields extends AllFieldsModel { + @Column + public int _order; + + public int getOrder() { + return _order; + } + + public void setOrder(int order) { + this._order = order; + } + + public SubclassAllFields(int order) { + super(null, 0, false, "", 0); + this._order = order; + } + } + + @Table(database = TestDatabase.class, assignDefaultValuesFromCursor = false) + public static class DontAssignDefaultModel { + @PrimaryKey + public String name; + @Column(getterName = "getNullableBool") + public boolean nullableBool; + @Column + public int _index; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isNullableBool() { + return nullableBool; + } + + public void setNullableBool(boolean nullableBool) { + this.nullableBool = nullableBool; + } + + public int getIndex() { + return _index; + } + + public void setIndex(int index) { + this._index = index; + } + + public DontAssignDefaultModel(String name, boolean nullableBool, int index) { + this.name = name; + this.nullableBool = nullableBool; + this._index = index; + } + } + + @Table(database = TestDatabase.class, orderedCursorLookUp = true) + public static class OrderCursorModel { + @Column + public int age; + @PrimaryKey + public int id; + @Column + public String name; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OrderCursorModel(int age, int id, String name) { + this.age = age; + this.id = id; + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class TypeConverterModel { + @PrimaryKey + public int id; + @Column(typeConverter = BlobConverter.class) + public byte[] opaqueData; + @Column + public Blob blob; + @Column(typeConverter = CustomTypeConverter.class) + @PrimaryKey + public CustomType customType; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public byte[] getOpaqueData() { + return opaqueData; + } + + public void setOpaqueData(byte[] opaqueData) { + this.opaqueData = opaqueData; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public CustomType getCustomType() { + return customType; + } + + public void setCustomType(CustomType customType) { + this.customType = customType; + } + + public TypeConverterModel(int id, byte[] opaqueData, Blob blob, CustomType customType) { + this.id = id; + this.opaqueData = opaqueData; + this.blob = blob; + this.customType = customType; + } + } + + @Table(database = TestDatabase.class) + public static class EnumTypeConverterModel { + @PrimaryKey + public int id; + @Column + public Blob blob; + @Column + public byte[] byteArray; + @Column(typeConverter = CustomEnumTypeConverter.class) + public Difficulty difficulty; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public byte[] getByteArray() { + return byteArray; + } + + public void setByteArray(byte[] byteArray) { + this.byteArray = byteArray; + } + + public Difficulty getDifficulty() { + return difficulty; + } + + public void setDifficulty(Difficulty difficulty) { + this.difficulty = difficulty; + } + + public EnumTypeConverterModel(int id, Blob blob, byte[] byteArray, Difficulty difficulty) { + this.id = id; + this.blob = blob; + this.byteArray = byteArray; + this.difficulty = difficulty; + } + } + + @Table(database = TestDatabase.class, allFields = true) + public static class FeedEntry { + @PrimaryKey + public int id; + public String title; + public String subtitle; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public FeedEntry(int id, String title, String subtitle) { + this.id = id; + this.title = title; + this.subtitle = subtitle; + } + } + + @Table(database = TestDatabase.class) + @ManyToMany( + generatedTableClassName = "Refund", referencedTable = Transfer.class, + referencedTableColumnName = "refund_in", thisTableColumnName = "refund_out", + saveForeignKeyModels = true + ) + public static class Transfer { + @PrimaryKey + public UUID transfer_id; + + public UUID getTransfer_id() { + return transfer_id; + } + + public void setTransfer_id(UUID transfer_id) { + this.transfer_id = transfer_id; + } + + public Transfer(UUID transfer_id) { + this.transfer_id = transfer_id; + } + } + + @Table(database = TestDatabase.class) + public static class Transfer2 { + @PrimaryKey + public UUID id; + @ForeignKey(stubbedRelationship = true) + public Account origin; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Account getOrigin() { + return origin; + } + + public void setOrigin(Account origin) { + this.origin = origin; + } + + public Transfer2(UUID id, Account origin) { + this.id = id; + this.origin = origin; + } + } + + @Table(database = TestDatabase.class) + public static class Account { + @PrimaryKey + public UUID id; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Account(UUID id) { + this.id = id; + } + } + + @Table(database = TestDatabase.class) + public static class SqlListenerModel implements SQLiteStatementListener { + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public SqlListenerModel(int id) { + this.id = id; + } + + @Override + public void onBindToInsertStatement(DatabaseStatement databaseStatement) { + + } + + @Override + public void onBindToUpdateStatement(DatabaseStatement databaseStatement) { + + } + + @Override + public void onBindToDeleteStatement(DatabaseStatement databaseStatement) { + + } + } + + public static class CustomType { + public int name; + + public int getName() { + return name; + } + + public void setName(int name) { + this.name = name; + } + + public CustomType(int name) { + this.name = name; + } + } + + public static class CustomTypeConverter extends TypeConverters.TypeConverter { + + public CustomTypeConverter() { + super(); + } + + @Override + public Integer getDBValue(CustomType model) { + return model != null ? model.name : null; + } + + @Override + public CustomType getModelValue(Integer data) { + if (data == null) { + return null; + } else { + return new CustomType(data); + } + } + } + + public static class CustomEnumTypeConverter extends TypeConverters.TypeConverter { + + public CustomEnumTypeConverter() { + super(); + } + + @Override + public String getDBValue(Difficulty model) { + return model != null ? model.name().substring(0, 1) : null; + } + + @Override + public Difficulty getModelValue(String data) { + switch (data) { + case "E": + return Difficulty.EASY; + case "M": + return Difficulty.MEDIUM; + case "H": + return Difficulty.HARD; + default: + return Difficulty.HARD; + } + } + } + + @TypeConverter + public static class BlobConverter extends TypeConverters.TypeConverter { + + public BlobConverter() { + super(); + } + + @Override + public Blob getDBValue(byte[] model) { + return model == null ? null : new Blob(model); + } + + @Override + public byte[] getModelValue(Blob data) { + return new byte[0]; + } + } + + @Table(database = TestDatabase.class) + public static class DefaultModel { + @PrimaryKey + @Column(defaultValue = "5") + public int id; + @Column(defaultValue = "5.0") + public Double location; + @Column(defaultValue = "\"String\"") + public String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Double getLocation() { + return location; + } + + public void setLocation(Double location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DefaultModel(int id, Double location, String name) { + this.id = id; + this.location = location; + this.name = name; + } + } + + @Table(database = TestDatabase.class, cachingEnabled = true) + public static class TestModelChild extends BaseModel { + @PrimaryKey + public long id; + @Column + public String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TestModelChild(long id, String name) { + this.id = id; + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class TestModelParent extends BaseModel { + @PrimaryKey + public long id; + + @Column + public String name; + + @ForeignKey(stubbedRelationship = true) + public TestModelChild child; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TestModelChild getChild() { + return child; + } + + public void setChild(TestModelChild child) { + this.child = child; + } + + public TestModelParent(long id, String name, TestModelChild child) { + this.id = id; + this.name = name; + this.child = child; + } + } + + @Table(database = TestDatabase.class) + public static class NullableNumbers { + @PrimaryKey + public int id; + @Column + public Float f; + @Column + public Double d; + @Column + public long l; + @Column + public int i; + @Column + public BigDecimal bigDecimal; + @Column + public BigInteger bigInteger; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Float getF() { + return f; + } + + public void setF(Float f) { + this.f = f; + } + + public Double getD() { + return d; + } + + public void setD(Double d) { + this.d = d; + } + + public long getL() { + return l; + } + + public void setL(long l) { + this.l = l; + } + + public int getI() { + return i; + } + + public void setI(int i) { + this.i = i; + } + + public BigDecimal getBigDecimal() { + return bigDecimal; + } + + public void setBigDecimal(BigDecimal bigDecimal) { + this.bigDecimal = bigDecimal; + } + + public BigInteger getBigInteger() { + return bigInteger; + } + + public void setBigInteger(BigInteger bigInteger) { + this.bigInteger = bigInteger; + } + + public NullableNumbers(int id, Float f, Double d, long l, int i, BigDecimal bigDecimal, BigInteger bigInteger) { + this.id = id; + this.f = f; + this.d = d; + this.l = l; + this.i = i; + this.bigDecimal = bigDecimal; + this.bigInteger = bigInteger; + } + } + + @Table(database = TestDatabase.class) + public static class NonNullKotlinModel { + @PrimaryKey + public String name; + @Column + public Date date; + @Column + public int numb; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public int getNumb() { + return numb; + } + + public void setNumb(int numb) { + this.numb = numb; + } + + public NonNullKotlinModel(String name, Date date, int numb) { + this.name = name; + this.date = date; + this.numb = numb; + } + } + + @Table(database = TestDatabase.class) + public static class Owner extends BaseModel { + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Owner(int id, String name) { + super(); + this.id = id; + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class Dog extends BaseModel { + @ForeignKey(onDelete = ForeignKeyAction.CASCADE, stubbedRelationship = true) + public Owner owner; + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String name; + + public Owner getOwner() { + return owner; + } + + public void setOwner(Owner owner) { + this.owner = owner; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Dog(Owner owner, int id, String name) { + super(); + this.owner = owner; + this.id = id; + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class Currency { + @PrimaryKey(autoincrement = true) + public long id; + @Column + @Unique + public String symbol; + @Column + public String shortName; + @Column + @Unique + public String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getSymbol() { + return symbol; + } + + public void setSymbol(String symbol) { + this.symbol = symbol; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Currency(long id, String symbol, String shortName, String name) { + this.id = id; + this.symbol = symbol; + this.shortName = shortName; + this.name = name; + } + } + + + public static class Password { + public String value; + + public Password(String value) { + this.value = value; + } + } + + public static class Email { + public String value; + + public Email(String value) { + this.value = value; + } + } + + @Table(database = TestDatabase.class) + public static class UserInfo { + @PrimaryKey + public Email email; + public Password password; + + public Email getEmail() { + return email; + } + + public void setEmail(Email email) { + this.email = email; + } + + public Password getPassword() { + return password; + } + + public void setPassword(Password password) { + this.password = password; + } + + public UserInfo() { + this(new Email(""), new Password("")); + } + + public UserInfo(Email email, Password password) { + this.email = email; + this.password = password; + } + } + + + @Table(database = TestDatabase.class) + public static class InternalClass { + @PrimaryKey + public String id; + + public InternalClass(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @Table(database = TestDatabase.class, uniqueColumnGroups = {@UniqueGroup(groupNumber = 1)}) + public static class UniqueModel { + @PrimaryKey + public String id; + @Unique(uniqueGroups = {1}) + public String name; + @ForeignKey + @Unique(uniqueGroups = {1}) + public TypeConverterModel model; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TypeConverterModel getModel() { + return model; + } + + public void setModel(TypeConverterModel model) { + this.model = model; + } + + public UniqueModel(String id, String name, TypeConverterModel model) { + this.id = id; + this.name = name; + this.model = model; + } + } + + @Table(database = TestDatabase.class) + @Fts3 + public static class Fts3Model { + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Fts3Model(String name) { + this.name = name; + } + } + + @Table(database = TestDatabase.class) + public static class Fts4Model { + @PrimaryKey(autoincrement = true) + public int id; + public String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Fts4Model(int id, String name) { + this.id = id; + this.name = name; + } + + } + + @Table(database = TestDatabase.class) + @Fts4(contentTable = Fts4Model.class) + public static class Fts4VirtualModel2 { + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Fts4VirtualModel2(String name) { + this.name = name; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModelsTest.java b/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModelsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d30fb06cb1521ab8dd51de027f4823448b016724 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SimpleTestModelsTest.java @@ -0,0 +1,20 @@ +package com.dbflow5.models; + +import com.dbflow5.config.FlowManager; + +import com.dbflow5.BaseUnitTest; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SimpleTestModelsTest extends BaseUnitTest { + @Test + public void validateCreationQuery() { + assertEquals("CREATE TABLE IF NOT EXISTS TypeConverterModel(" + + "id INTEGER, " + + "opaqueData BLOB, " + + "blob BLOB, " + + "customType INTEGER, " + + "PRIMARY KEY(id, customType))", FlowManager.getModelAdapter(SimpleTestModels.TypeConverterModel.class).getCreationQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Song_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Song_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..2849674b17103706ff629953fde595bf0eb0d863 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Song_Table.java @@ -0,0 +1,149 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.ManyToMany.Song; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class Song_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(Song.class, "id"); + + public static final Property name = new Property(Song.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public Song_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Song.class; + } + + @Override + public final String getName() { + return "Song"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(Song model, Number id) { + model.setId(id.intValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Song model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Song model) { + statement.bindLong(1, (long)model.getId()); + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + statement.bindLong(3, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Song model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Song(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Song(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Song SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Song WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Song(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final Song loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Song model = new Song(0, ""); + model.setId(cursor.getIntOrDefault("id")); + model.setName(cursor.getStringOrDefault("name", "")); + return model; + } + + @Override + public final boolean exists(Song model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(Song.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Song model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SqlListenerModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/SqlListenerModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..394df9b015b7a73300b5f0216444674681d4993b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SqlListenerModel_Table.java @@ -0,0 +1,120 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.SqlListenerModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class SqlListenerModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(SqlListenerModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public SqlListenerModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SqlListenerModel.class; + } + + @Override + public final String getName() { + return "SqlListenerModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, SqlListenerModel model) { + statement.bindLong(1, (long)model.getId()); + model.onBindToInsertStatement(statement); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, SqlListenerModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + model.onBindToUpdateStatement(statement); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, SqlListenerModel model) { + statement.bindLong(1, (long)model.getId()); + model.onBindToDeleteStatement(statement); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SqlListenerModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SqlListenerModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SqlListenerModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SqlListenerModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SqlListenerModel(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final SqlListenerModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SqlListenerModel model = new SqlListenerModel(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SqlListenerModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/SubclassAllFields_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/SubclassAllFields_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..be303b861713e9f74393994a555cae0f5a3fc0be --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/SubclassAllFields_Table.java @@ -0,0 +1,147 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Boolean; +import com.dbflow5.models.SimpleTestModels.SubclassAllFields; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class SubclassAllFields_Table extends ModelAdapter { + public static final Property order = new Property(SubclassAllFields.class, "_order"); + + /** + * Primary Key */ + public static final Property name = new Property(SubclassAllFields.class, "name"); + + public static final Property count = new Property(SubclassAllFields.class, "count"); + + public static final Property truth = new Property(SubclassAllFields.class, "truth"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{order,name,count,truth}; + + public SubclassAllFields_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SubclassAllFields.class; + } + + @Override + public final String getName() { + return "SubclassAllFields"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "_order": { + return order; + } + case "name": { + return name; + } + case "count": { + return count; + } + case "truth": { + return truth; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, SubclassAllFields model) { + statement.bindLong(1, (long)model.getOrder()); + statement.bindStringOrNull(2, model.getName()); + statement.bindNumberOrNull(3, model.getCount()); + statement.bindLong(4, model.isTruth() ? 1L : 0L); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, SubclassAllFields model) { + statement.bindLong(1, (long)model.getOrder()); + statement.bindStringOrNull(2, model.getName()); + statement.bindNumberOrNull(3, model.getCount()); + statement.bindLong(4, model.isTruth() ? 1L : 0L); + statement.bindStringOrNull(5, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, SubclassAllFields model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SubclassAllFields(_order,name,count,truth) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SubclassAllFields(_order,name,count,truth) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SubclassAllFields SET _order=?,name=?,count=?,truth=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SubclassAllFields WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SubclassAllFields(_order INTEGER, name TEXT, count INTEGER, truth INTEGER, PRIMARY KEY(name))"; + } + + @Override + public final SubclassAllFields loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SubclassAllFields model = new SubclassAllFields(0); + model.setOrder(cursor.getIntOrDefault("_order")); + model.setName(cursor.getStringOrDefault("name")); + model.setCount(cursor.getIntOrDefault("count", 0)); + int index_truth = cursor.getColumnIndexForName("truth"); + if (index_truth != -1 && !cursor.isColumnNull(index_truth)) { + model.setTruth(cursor.getBoolean(index_truth)); + } else { + model.setTruth(false); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SubclassAllFields model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TempModelTest.java b/entry/src/ohosTest/java/com/dbflow5/models/TempModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cfca80f1badf494081ecbebb707d42569d62c844 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TempModelTest.java @@ -0,0 +1,43 @@ +package com.dbflow5.models; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.config.FlowManager; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.structure.Model; +import org.junit.Test; + +public class TempModelTest extends BaseUnitTest { + @Table(database = TestDatabase.class, temporary = true, createWithDatabase = false) + public static class TempModel{ + @PrimaryKey + public int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public TempModel(int id) { + this.id = id; + } + } + + @Test + public void createTempTable() { + FlowManager.database(TestDatabase.class, db -> { + FlowManager.modelAdapter(TempModel.class).createIfNotExists(db); + + Model.save(TempModel.class, new TempModel(5), db); + + FlowManager.modelAdapter(TempModel.class).drop(db, false); + + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TempModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/TempModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..5fdc325502c06fecf7ce886fc4975bad684f7e8d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TempModel_Table.java @@ -0,0 +1,122 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.TempModelTest.TempModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class TempModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(TempModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public TempModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TempModel.class; + } + + @Override + public final String getName() { + return "TempModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final boolean createWithDatabase() { + return false; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TempModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TempModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TempModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TempModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TempModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TempModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TempModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TEMP TABLE IF NOT EXISTS TempModel(id INTEGER, PRIMARY KEY(id))"; + } + + @Override + public final TempModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TempModel model = new TempModel(0); + model.setId(cursor.getIntOrDefault("id")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TempModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TestModelChild_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/TestModelChild_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..e38ace7c26d5f17f03e8b17d614257c46eabf058 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TestModelChild_Table.java @@ -0,0 +1,251 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleKeyCacheableListModelLoader; +import com.dbflow5.adapter.queriable.SingleKeyCacheableModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.CacheableListModelSaver; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.cache.SimpleMapCache; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.TestModelChild; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Collection; + +public final class TestModelChild_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(TestModelChild.class, "id"); + + public static final Property name = new Property(TestModelChild.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public static final CacheAdapter cacheAdapter = new CacheAdapter(new SimpleMapCache(25), 1, null) { + @Override + public final Object getCachingColumnValueFromModel(TestModelChild model) { + return model.getId(); + } + + @Override + public final Object getCachingColumnValueFromCursor(FlowCursor cursor) { + return cursor.getLong(cursor.getColumnIndexForName("id")); + } + + @Override + public final Object getCachingId(TestModelChild model) { + return getCachingColumnValueFromModel(model); + } + + @Override + public Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor) { + return new Object[0]; + } + + @Override + public Object[] getCachingColumnValuesFromModel(Object[] inValues, TestModelChild TModel) { + return new Object[0]; + } + + @Override + public void reloadRelationships(TestModelChild model, FlowCursor cursor, DatabaseWrapper databaseWrapper) { + } + }; + + public TestModelChild_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TestModelChild.class; + } + + @Override + public final String getName() { + return "TestModelChild"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final SingleModelLoader createSingleModelLoader() { + return new SingleKeyCacheableModelLoader<>(table(), cacheAdapter); + } + + @Override + public final ListModelLoader createListModelLoader() { + return new SingleKeyCacheableListModelLoader<>(table(), cacheAdapter); + } + + @Override + protected CacheableListModelSaver createListModelSaver() { + return new CacheableListModelSaver<>(getModelSaver(), cacheAdapter); + } + + @Override + public final boolean cachingEnabled() { + return true; + } + + @Override + public final TestModelChild load(TestModelChild model, DatabaseWrapper wrapper) { + TestModelChild loaded = super.load(model, wrapper); + cacheAdapter.storeModelInCache(model); + return loaded; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TestModelChild model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TestModelChild model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + statement.bindLong(3, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TestModelChild model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TestModelChild(id,name) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TestModelChild(id,name) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TestModelChild SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TestModelChild WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS TestModelChild(id INTEGER, name TEXT, PRIMARY KEY(id))"; + } + + @Override + public final TestModelChild loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TestModelChild model = new TestModelChild(0,null); + model.setId(cursor.getLongOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TestModelChild model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } + + @Override + public final boolean delete(TestModelChild model, DatabaseWrapper wrapper) { + cacheAdapter.removeModelFromCache(model); + boolean successful = super.delete(model, wrapper); + return successful; + } + + @Override + public final long deleteAll(Collection models, + DatabaseWrapper wrapper) { + cacheAdapter.removeModelsFromCache(models); + long successful = super.deleteAll(models, wrapper); + return successful; + } + + @Override + public final boolean save(TestModelChild model, DatabaseWrapper wrapper) { + boolean successful = super.save(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long saveAll(Collection models, DatabaseWrapper wrapper) { + long count = super.saveAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final long insert(TestModelChild model, DatabaseWrapper wrapper) { + long rowId = super.insert(model, wrapper); + cacheAdapter.storeModelInCache(model); + return rowId; + } + + @Override + public final long insertAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.insertAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } + + @Override + public final boolean update(TestModelChild model, DatabaseWrapper wrapper) { + boolean successful = super.update(model, wrapper); + cacheAdapter.storeModelInCache(model); + return successful; + } + + @Override + public final long updateAll(Collection models, + DatabaseWrapper wrapper) { + long count = super.updateAll(models, wrapper); + cacheAdapter.storeModelsInCache(models); + return count; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TestModelParent_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/TestModelParent_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..140f3e449c95d6d39ac6ad10d42d51ddf5e71eb8 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TestModelParent_Table.java @@ -0,0 +1,149 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.TestModelParent; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Override; +import java.lang.String; + +public final class TestModelParent_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(TestModelParent.class, "id"); + + public static final Property name = new Property(TestModelParent.class, "name"); + + /** + * Foreign Key */ + public static final Property child_id = new Property(TestModelParent.class, "child_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,child_id}; + + public TestModelParent_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TestModelParent.class; + } + + @Override + public final String getName() { + return "TestModelParent"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "child_id": { + return child_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TestModelParent model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + if (model.getChild() != null) { + statement.bindLong(3, model.getChild().getId()); + } else { + statement.bindNull(3); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TestModelParent model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + if (model.getChild() != null) { + statement.bindLong(3, model.getChild().getId()); + } else { + statement.bindNull(3); + } + statement.bindLong(4, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TestModelParent model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TestModelParent(id,name,child_id) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TestModelParent(id,name,child_id) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TestModelParent SET id=?,name=?,child_id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TestModelParent WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS TestModelParent(id INTEGER, name TEXT, child_id INTEGER, PRIMARY KEY(id), FOREIGN KEY(child_id) REFERENCES TestModelChild (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final TestModelParent loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TestModelParent model = new TestModelParent(0,null,null); + model.setId(cursor.getLongOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + int index_child_id_TestModelChild_Table = cursor.getColumnIndexForName("child_id"); + if (index_child_id_TestModelChild_Table != -1 && !cursor.isColumnNull(index_child_id_TestModelChild_Table)) { + model.setChild(new SimpleTestModels.TestModelChild(0,null)); + model.getChild().setId(cursor.getLong(index_child_id_TestModelChild_Table)); + } else { + model.setChild(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TestModelParent model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Transfer2_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Transfer2_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..23efbf59f9c5a0d9281448d3a6198588114d4a84 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Transfer2_Table.java @@ -0,0 +1,167 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.converter.TypeConverters.UUIDConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.Transfer2; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import java.util.UUID; + +public final class Transfer2_Table extends ModelAdapter { + /** + * Primary Key */ + public static final TypeConvertedProperty id = new TypeConvertedProperty(Transfer2.class, "id", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Transfer2_Table adapter = (Transfer2_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterUUIDConverter; + } + }); + + /** + * Foreign Key */ + public static final Property origin_id = new Property(Transfer2.class, "origin_id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,origin_id}; + + private final UUIDConverter global_typeConverterUUIDConverter; + + public Transfer2_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterUUIDConverter = (UUIDConverter) holder.getTypeConverterForClass(UUID.class); + } + + @Override + public final Class table() { + return Transfer2.class; + } + + @Override + public final String getName() { + return "Transfer2"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "origin_id": { + return origin_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Transfer2 model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.getId()); + statement.bindStringOrNull(1, refid); + if (model.getOrigin() != null) { + String originrefid = global_typeConverterUUIDConverter.getDBValue(model.getOrigin().getId()); + statement.bindStringOrNull(2, originrefid); + } else { + statement.bindNull(2); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Transfer2 model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.getId()); + statement.bindStringOrNull(1, refid); + if (model.getOrigin() != null) { + String origin_refid = global_typeConverterUUIDConverter.getDBValue(model.getOrigin().getId()); + statement.bindStringOrNull(2, origin_refid); + } else { + statement.bindNull(2); + } + statement.bindStringOrNull(3, refid); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Transfer2 model) { + String refid = global_typeConverterUUIDConverter.getDBValue(model.getId()); + statement.bindStringOrNull(1, refid); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Transfer2(id,origin_id) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Transfer2(id,origin_id) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Transfer2 SET id=?,origin_id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Transfer2 WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Transfer2(id TEXT, origin_id TEXT, PRIMARY KEY(id), FOREIGN KEY(origin_id) REFERENCES Account (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final Transfer2 loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Transfer2 model = new Transfer2(UUID.randomUUID(), null); + int index_id = cursor.getColumnIndexForName("id"); + if (index_id != -1 && !cursor.isColumnNull(index_id)) { + model.setId(global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_id))); + } + int index_origin_id_Account_Table = cursor.getColumnIndexForName("origin_id"); + if (index_origin_id_Account_Table != -1 && !cursor.isColumnNull(index_origin_id_Account_Table)) { + model.setOrigin(new SimpleTestModels.Account(UUID.randomUUID())); + model.getOrigin().setId(global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_origin_id_Account_Table))); + } else { + model.setOrigin(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Transfer2 model) { + OperatorGroup clause = OperatorGroup.clause(); + String refid = global_typeConverterUUIDConverter.getDBValue(model.getId()); + clause.and(id.invertProperty().eq(refid)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/Transfer_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/Transfer_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..864f829b258eb611805b42b4ea8ab9e795b269a6 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/Transfer_Table.java @@ -0,0 +1,141 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseHolder; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.converter.TypeConverters.UUIDConverter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.models.SimpleTestModels.Transfer; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import java.util.UUID; + +public final class Transfer_Table extends ModelAdapter { + /** + * Primary Key */ + public static final TypeConvertedProperty transfer_id = new TypeConvertedProperty(Transfer.class, "transfer_id", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + Transfer_Table adapter = (Transfer_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.global_typeConverterUUIDConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{transfer_id}; + + private final UUIDConverter global_typeConverterUUIDConverter; + + public Transfer_Table(DatabaseHolder holder, DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + global_typeConverterUUIDConverter = (UUIDConverter) holder.getTypeConverterForClass(UUID.class); + } + + @Override + public final Class table() { + return Transfer.class; + } + + @Override + public final String getName() { + return "Transfer"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "transfer_id": { + return transfer_id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Transfer model) { + String reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.getTransfer_id()); + statement.bindStringOrNull(1, reftransfer_id); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Transfer model) { + String reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.getTransfer_id()); + statement.bindStringOrNull(1, reftransfer_id); + statement.bindStringOrNull(2, reftransfer_id); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Transfer model) { + String reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.getTransfer_id()); + statement.bindStringOrNull(1, reftransfer_id); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Transfer(transfer_id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Transfer(transfer_id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Transfer SET transfer_id=? WHERE transfer_id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Transfer WHERE transfer_id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Transfer(transfer_id TEXT, PRIMARY KEY(transfer_id))"; + } + + @Override + public final Transfer loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Transfer model = new Transfer(UUID.randomUUID()); + int index_transfer_id = cursor.getColumnIndexForName("transfer_id"); + if (index_transfer_id != -1 && !cursor.isColumnNull(index_transfer_id)) { + model.setTransfer_id(global_typeConverterUUIDConverter.getModelValue(cursor.getString(index_transfer_id))); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Transfer model) { + OperatorGroup clause = OperatorGroup.clause(); + String reftransfer_id = global_typeConverterUUIDConverter.getDBValue(model.getTransfer_id()); + clause.and(transfer_id.invertProperty().eq(reftransfer_id)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TwoColumnModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/TwoColumnModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ef9463a47aa78b8c94df8db33e73aa4cc9fb1950 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TwoColumnModel_Table.java @@ -0,0 +1,125 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.TwoColumnModel; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class TwoColumnModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property name = new Property(TwoColumnModel.class, "name"); + + public static final Property id = new Property(TwoColumnModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,id}; + + public TwoColumnModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TwoColumnModel.class; + } + + @Override + public final String getName() { + return "TwoColumnModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TwoColumnModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindLong(2, (long)model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TwoColumnModel model) { + statement.bindStringOrNull(1, model.getName()); + statement.bindLong(2, (long)model.getId()); + statement.bindStringOrNull(3, model.getName()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TwoColumnModel model) { + statement.bindStringOrNull(1, model.getName()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TwoColumnModel(name,id) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TwoColumnModel(name,id) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TwoColumnModel SET name=?,id=? WHERE name=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TwoColumnModel WHERE name=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS TwoColumnModel(name TEXT, id INTEGER, PRIMARY KEY(name))"; + } + + @Override + public final TwoColumnModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TwoColumnModel model = new TwoColumnModel("",0); + model.setName(cursor.getStringOrDefault("name")); + model.setId(cursor.getIntOrDefault("id", (int) 0)); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TwoColumnModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(name.eq(model.getName())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/TypeConverterModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/TypeConverterModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..6dd38e07fa62da4147d6bdadf2e8da459baf7309 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/TypeConverterModel_Table.java @@ -0,0 +1,197 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters.TypeConverter; +import com.dbflow5.data.Blob; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.query.property.TypeConvertedProperty.TypeConverterGetter; +import com.dbflow5.query.property.WrapperProperty; +import com.dbflow5.models.SimpleTestModels.TypeConverterModel; +import com.dbflow5.models.SimpleTestModels.CustomType; +import com.dbflow5.models.SimpleTestModels.CustomTypeConverter; +import com.dbflow5.models.SimpleTestModels.BlobConverter; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class TypeConverterModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(TypeConverterModel.class, "id"); + + public static final TypeConvertedProperty opaqueData = new TypeConvertedProperty(TypeConverterModel.class, "opaqueData", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + TypeConverterModel_Table adapter = (TypeConverterModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.typeConverterBlobConverter; + } + }); + + public static final WrapperProperty blob = new WrapperProperty(TypeConverterModel.class, "blob"); + + /** + * Primary Key */ + public static final TypeConvertedProperty customType = new TypeConvertedProperty(TypeConverterModel.class, "customType", true, + new TypeConverterGetter() { + @Override + public TypeConverter getTypeConverter(Class modelClass) { + TypeConverterModel_Table adapter = (TypeConverterModel_Table) FlowManager.getRetrievalAdapter(modelClass); + return adapter.typeConverterCustomTypeConverter; + } + }); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,opaqueData,blob,customType}; + + private final CustomTypeConverter typeConverterCustomTypeConverter = new CustomTypeConverter(); + + private final BlobConverter typeConverterBlobConverter = new BlobConverter(); + + public TypeConverterModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TypeConverterModel.class; + } + + @Override + public final String getName() { + return "TypeConverterModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "opaqueData": { + return opaqueData; + } + case "blob": { + return blob; + } + case "customType": { + return customType; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + Blob refopaqueData = typeConverterBlobConverter.getDBValue(model.getOpaqueData()); + statement.bindBlobOrNull(2, refopaqueData != null ? refopaqueData.getBlob() : null); + byte[] refblob = model.getBlob() != null ? model.getBlob().getBlob() : null; + statement.bindBlobOrNull(3, refblob); + Integer refcustomType = typeConverterCustomTypeConverter.getDBValue(model.getCustomType()); + statement.bindNumberOrNull(4, refcustomType); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + Blob refopaqueData = typeConverterBlobConverter.getDBValue(model.getOpaqueData()); + statement.bindBlobOrNull(2, refopaqueData != null ? refopaqueData.getBlob() : null); + byte[] refblob = model.getBlob() != null ? model.getBlob().getBlob() : null; + statement.bindBlobOrNull(3, refblob); + Integer refcustomType = typeConverterCustomTypeConverter.getDBValue(model.getCustomType()); + statement.bindNumberOrNull(4, refcustomType); + statement.bindLong(5, (long)model.getId()); + statement.bindNumberOrNull(6, refcustomType); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TypeConverterModel model) { + statement.bindLong(1, (long)model.getId()); + Integer refcustomType = typeConverterCustomTypeConverter.getDBValue(model.getCustomType()); + statement.bindNumberOrNull(2, refcustomType); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TypeConverterModel(id,opaqueData,blob,customType) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TypeConverterModel(id,opaqueData,blob,customType) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TypeConverterModel SET id=?,opaqueData=?,blob=?,customType=? WHERE id=? AND customType=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TypeConverterModel WHERE id=? AND customType=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS TypeConverterModel(id INTEGER, opaqueData BLOB, blob BLOB, customType INTEGER, PRIMARY KEY(id, customType))"; + } + + @Override + public final TypeConverterModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TypeConverterModel model = new TypeConverterModel(0,null,null,null); + model.setId(cursor.getIntOrDefault("id")); + int index_opaqueData = cursor.getColumnIndexForName("opaqueData"); + if (index_opaqueData != -1 && !cursor.isColumnNull(index_opaqueData)) { + model.setOpaqueData(typeConverterBlobConverter.getModelValue(new Blob(cursor.getBlob(index_opaqueData)))); + } else { + model.setOpaqueData(typeConverterBlobConverter.getModelValue(null)); + } + int index_blob = cursor.getColumnIndexForName("blob"); + if (index_blob != -1 && !cursor.isColumnNull(index_blob)) { + model.setBlob(new Blob(cursor.getBlob(index_blob))); + } else { + model.setBlob(null); + } + int index_customType = cursor.getColumnIndexForName("customType"); + if (index_customType != -1 && !cursor.isColumnNull(index_customType)) { + model.setCustomType(typeConverterCustomTypeConverter.getModelValue(cursor.getInt(index_customType))); + } else { + model.setCustomType(typeConverterCustomTypeConverter.getModelValue(null)); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TypeConverterModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + Integer refcustomType = typeConverterCustomTypeConverter.getDBValue(model.getCustomType()); + clause.and(customType.invertProperty().eq(refcustomType)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/UniqueModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/UniqueModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..1eb4f8158c245eb920313015084c83986eb7fe3f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/UniqueModel_Table.java @@ -0,0 +1,193 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.UniqueModel; +import com.dbflow5.models.SimpleTestModels.CustomType; +import com.dbflow5.models.SimpleTestModels.CustomTypeConverter; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class UniqueModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(UniqueModel.class, "id"); + + public static final Property name = new Property(UniqueModel.class, "name"); + + /** + * Foreign Key */ + public static final Property model_id = new Property(UniqueModel.class, "model_id"); + + /** + * Foreign Key */ + public static final Property model_customType = new Property(UniqueModel.class, "model_customType"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name,model_id,model_customType}; + + private final CustomTypeConverter typeConverterCustomTypeConverter = new CustomTypeConverter(); + + public UniqueModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return UniqueModel.class; + } + + @Override + public final String getName() { + return "UniqueModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + case "model_id": { + return model_id; + } + case "model_customType": { + return model_customType; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, UniqueModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getModel() != null) { + statement.bindLong(3, (long)model.getModel().getId()); + Integer modelrefcustomType = typeConverterCustomTypeConverter.getDBValue(model.getModel().getCustomType()); + statement.bindNumberOrNull(4, modelrefcustomType); + } else { + statement.bindNull(3); + statement.bindNull(4); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, UniqueModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getName() != null) { + statement.bindString(2, model.getName()); + } else { + statement.bindString(2, ""); + } + if (model.getModel() != null) { + statement.bindLong(3, (long)model.getModel().getId()); + Integer model_refcustomType = typeConverterCustomTypeConverter.getDBValue(model.getModel().getCustomType()); + statement.bindNumberOrNull(4, model_refcustomType); + } else { + statement.bindNull(3); + statement.bindNull(4); + } + if (model.getId() != null) { + statement.bindString(5, model.getId()); + } else { + statement.bindString(5, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, UniqueModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO UniqueModel(id,name,model_id,model_customType) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO UniqueModel(id,name,model_id,model_customType) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE UniqueModel SET id=?,name=?,model_id=?,model_customType=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM UniqueModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS UniqueModel(id TEXT, name TEXT UNIQUE ON CONFLICT FAIL, model_id INTEGER ,model_customType INTEGER, UNIQUE(name, model_id, model_customType) ON CONFLICT FAIL, PRIMARY KEY(id), FOREIGN KEY(model_id, model_customType) REFERENCES TypeConverterModel (id, customType) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final UniqueModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + UniqueModel model = new UniqueModel("","",null); + model.setId(cursor.getStringOrDefault("id", "")); + model.setName(cursor.getStringOrDefault("name", "")); + int index_model_id_TypeConverterModel_Table = cursor.getColumnIndexForName("model_id"); + int index_model_customType_TypeConverterModel_Table = cursor.getColumnIndexForName("model_customType"); + if (index_model_id_TypeConverterModel_Table != -1 && !cursor.isColumnNull(index_model_id_TypeConverterModel_Table) && index_model_customType_TypeConverterModel_Table != -1 && !cursor.isColumnNull(index_model_customType_TypeConverterModel_Table)) { + model.setModel(com.dbflow5.query.SQLite.select().from(SimpleTestModels.TypeConverterModel.class).where() + .and(TypeConverterModel_Table.id.eq(cursor.getInt(index_model_id_TypeConverterModel_Table))) + .and(TypeConverterModel_Table.customType.eq(typeConverterCustomTypeConverter.getModelValue(cursor.getInt(index_model_customType_TypeConverterModel_Table)))) + .querySingle(wrapper)); + } else { + model.setModel(null); + } + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(UniqueModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/UserInfo_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/UserInfo_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..d7aae5b5581642c54b3e90dff899f22224655f2f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/UserInfo_Table.java @@ -0,0 +1,148 @@ +package com.dbflow5.models; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.models.SimpleTestModels.UserInfo; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class UserInfo_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property email = new Property(UserInfo.class, "email"); + + public static final Property password = new Property(UserInfo.class, "password"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{email,password}; + + public UserInfo_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return UserInfo.class; + } + + @Override + public final String getName() { + return "UserInfo"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "email": { + return email; + } + case "password": { + return password; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, UserInfo model) { + if (model.getEmail() != null) { + statement.bindString(1, model.getEmail().value); + } else { + statement.bindString(1, ""); + } + if (model.getPassword() != null) { + statement.bindString(2, model.getPassword().value); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, UserInfo model) { + if (model.getEmail() != null) { + statement.bindString(1, model.getEmail().value); + } else { + statement.bindString(1, ""); + } + if (model.getPassword() != null) { + statement.bindString(2, model.getPassword().value); + } else { + statement.bindString(2, ""); + } + if (model.getEmail() != null) { + statement.bindString(3, model.getEmail().value); + } else { + statement.bindString(3, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, UserInfo model) { + if (model.getEmail() != null) { + statement.bindString(1, model.getEmail().value); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO UserInfo(email,password) VALUES (?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO UserInfo(email,password) VALUES (?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE UserInfo SET email=?,password=? WHERE email=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM UserInfo WHERE email=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS UserInfo(email TEXT, password TEXT, PRIMARY KEY(email))"; + } + + @Override + public final UserInfo loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + UserInfo model = new UserInfo(); + model.setEmail(new SimpleTestModels.Email(cursor.getStringOrDefault("email", ""))); + model.setPassword(new SimpleTestModels.Password(cursor.getStringOrDefault("password", ""))); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(UserInfo model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(email.eq(model.getEmail().value)); + return clause; + } +} diff --git a/tests/src/androidTest/java/com/dbflow5/models/java/DatabaseModel.java b/entry/src/ohosTest/java/com/dbflow5/models/java/DatabaseModel.java similarity index 100% rename from tests/src/androidTest/java/com/dbflow5/models/java/DatabaseModel.java rename to entry/src/ohosTest/java/com/dbflow5/models/java/DatabaseModel.java diff --git a/tests/src/androidTest/java/com/dbflow5/models/java/JavaModel.java b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel.java similarity index 100% rename from tests/src/androidTest/java/com/dbflow5/models/java/JavaModel.java rename to entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel.java index 3fe963e80491b2e65dcdb2a223c6c8e00e7984cb..4eb8858a709dd4cc4d6f688b44a0bac27a1c4a1f 100644 --- a/tests/src/androidTest/java/com/dbflow5/models/java/JavaModel.java +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel.java @@ -1,8 +1,8 @@ package com.dbflow5.models.java; -import com.dbflow5.TestDatabase; import com.dbflow5.annotation.PrimaryKey; import com.dbflow5.annotation.Table; +import com.dbflow5.TestDatabase; @Table(database = TestDatabase.class) public class JavaModel { diff --git a/tests/src/androidTest/java/com/dbflow5/models/java/JavaModelView.java b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView.java similarity index 92% rename from tests/src/androidTest/java/com/dbflow5/models/java/JavaModelView.java rename to entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView.java index e6f7886308cf5bde11424b57b1af644347944ba4..4949f34a2dafcb358b991db1f44f44b7b473bc05 100644 --- a/tests/src/androidTest/java/com/dbflow5/models/java/JavaModelView.java +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView.java @@ -1,13 +1,12 @@ package com.dbflow5.models.java; -import com.dbflow5.TestDatabase; import com.dbflow5.annotation.Column; import com.dbflow5.annotation.ModelView; import com.dbflow5.annotation.ModelViewQuery; -import com.dbflow5.database.DatabaseWrapper; import com.dbflow5.models.Author_Table; import com.dbflow5.query.SQLite; import com.dbflow5.sql.Query; +import com.dbflow5.TestDatabase; @ModelView(database = TestDatabase.class) public class JavaModelView { diff --git a/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView_ViewTable.java b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView_ViewTable.java new file mode 100644 index 0000000000000000000000000000000000000000..c51926b230707b56ecd80e830771b627268fe178 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModelView_ViewTable.java @@ -0,0 +1,61 @@ +package com.dbflow5.models.java; + +import com.dbflow5.adapter.ModelViewAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class JavaModelView_ViewTable extends ModelViewAdapter { + public static final String VIEW_NAME = "JavaModelView"; + + public static final Property id = new Property(JavaModelView.class, "id"); + + public static final Property firstName = new Property(JavaModelView.class, "firstName"); + + public JavaModelView_ViewTable(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return JavaModelView.class; + } + + @Override + public final String getCreationQuery() { + return "CREATE VIEW IF NOT EXISTS JavaModelView AS " + JavaModelView.getQuery().getQuery(); + } + + @Override + public final String getName() { + return "JavaModelView"; + } + + @Override + public final ObjectType getType() { + return ObjectType.View; + } + + @Override + public final JavaModelView loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + JavaModelView model = new JavaModelView(); + model.id = cursor.getStringOrDefault("id"); + model.firstName = cursor.getIntOrDefault("firstName", 0); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(JavaModelView model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.id)); + clause.and(firstName.eq(model.firstName)); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Helper.java b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Helper.java new file mode 100644 index 0000000000000000000000000000000000000000..d7a5db5770eed0b01ee2c6c66f5cc14dacdb1511 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Helper.java @@ -0,0 +1,13 @@ +package com.dbflow5.models.java; + +import java.lang.String; + +public final class JavaModel_Helper { + public static final String getId(JavaModel model) { + return model.id; + } + + public static final void setId(JavaModel model, String var) { + model.id = var; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..4719eeb087003f4fca72717781d4f8cbc792aa06 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/JavaModel_Table.java @@ -0,0 +1,120 @@ +package com.dbflow5.models.java; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class JavaModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(JavaModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public JavaModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return JavaModel.class; + } + + @Override + public final String getName() { + return "JavaModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, JavaModel model) { + statement.bindStringOrNull(1, model.id); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, JavaModel model) { + statement.bindStringOrNull(1, model.id); + statement.bindStringOrNull(2, model.id); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, JavaModel model) { + statement.bindStringOrNull(1, model.id); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO JavaModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO JavaModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE JavaModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM JavaModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS JavaModel(id TEXT, PRIMARY KEY(id))"; + } + + @Override + public final JavaModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + JavaModel model = new JavaModel(); + model.id = cursor.getStringOrDefault("id"); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(JavaModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.id)); + return clause; + } +} diff --git a/tests/src/androidTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java b/entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java similarity index 100% rename from tests/src/androidTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java rename to entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java index be9aa40a3e2d3186ee970f95699adc2f374246df..0f39ac6500a4fe6e0105eae48cb641373c3bdec4 100644 --- a/tests/src/androidTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel.java @@ -1,9 +1,9 @@ package com.dbflow5.models.java.otherpackage; -import com.dbflow5.TestDatabase; import com.dbflow5.annotation.Column; import com.dbflow5.annotation.ForeignKey; import com.dbflow5.annotation.Table; +import com.dbflow5.TestDatabase; import com.dbflow5.models.java.DatabaseModel; import com.dbflow5.models.java.JavaModel; diff --git a/entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..7ee365f858c43fd7a9b75b8617e25a5bff2114e8 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/models/java/otherpackage/ExampleModel_Table.java @@ -0,0 +1,157 @@ +package com.dbflow5.models.java.otherpackage; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.models.java.JavaModel; +import com.dbflow5.models.java.JavaModel_Table; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class ExampleModel_Table extends ModelAdapter { + public static final Property name = new Property(ExampleModel.class, "name"); + + /** + * Foreign Key */ + public static final Property model_id = new Property(ExampleModel.class, "model_id"); + + /** + * Primary Key */ + public static final Property id = new Property(ExampleModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{name,model_id,id}; + + public ExampleModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return ExampleModel.class; + } + + @Override + public final String getName() { + return "ExampleModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "name": { + return name; + } + case "model_id": { + return model_id; + } + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, ExampleModel model) { + statement.bindStringOrNull(1, model.name); + if (model.model != null) { + statement.bindStringOrNull(2, com.dbflow5.models.java.JavaModel_Helper.getId(model.model)); + } else { + statement.bindNull(2); + } + statement.bindNumberOrNull(3, model.getId()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, ExampleModel model) { + statement.bindStringOrNull(1, model.name); + if (model.model != null) { + statement.bindStringOrNull(2, com.dbflow5.models.java.JavaModel_Helper.getId(model.model)); + } else { + statement.bindNull(2); + } + statement.bindNumberOrNull(3, model.getId()); + statement.bindNumberOrNull(4, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, ExampleModel model) { + statement.bindNumberOrNull(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO ExampleModel(name,model_id,id) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO ExampleModel(name,model_id,id) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE ExampleModel SET name=?,model_id=?,id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM ExampleModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS ExampleModel(name TEXT, model_id TEXT, id INTEGER, PRIMARY KEY(id), FOREIGN KEY(model_id) REFERENCES JavaModel (id) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final ExampleModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + ExampleModel model = new ExampleModel(); + model.name = cursor.getStringOrDefault("name"); + int index_model_id_JavaModel_Table = cursor.getColumnIndexForName("model_id"); + if (index_model_id_JavaModel_Table != -1 && !cursor.isColumnNull(index_model_id_JavaModel_Table)) { + model.model = SQLite.select().from(JavaModel.class).where() + .and(JavaModel_Table.id.eq(cursor.getString(index_model_id_JavaModel_Table))) + .querySingle(wrapper); + } else { + model.model = null; + } + model.setId(cursor.getIntOrDefault("id", 0)); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(ExampleModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/paging/QueryDataSourceTest.java b/entry/src/ohosTest/java/com/dbflow5/paging/QueryDataSourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0f813cb139ce14d5c563c55be488ce1318ba1415 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/paging/QueryDataSourceTest.java @@ -0,0 +1,61 @@ +package com.dbflow5.paging; + +import com.dbflow5.TestDatabase; +import com.dbflow5.TestExtensions; +import com.dbflow5.TestForeignKeyDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.query.From; +import com.dbflow5.query.SQLite; +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.Set; +import com.dbflow5.structure.Model; +import org.junit.Assert; +import org.junit.Test; + +public class QueryDataSourceTest extends BaseUnitTest { + @Test + public void testLoadInitialParams() { + FlowManager.database(TestDatabase.class, db -> { + for(int i=0;i<100;i++) { + Model.save(TestForeignKeyDatabase.SimpleModel.class, new TestForeignKeyDatabase.SimpleModel(String.valueOf(i)), db); + } + + QueryDataSource.Factory> factory = QueryDataSource.toDataSourceFactory(SQLite.select().from(SimpleTestModels.SimpleModel.class), db); + + PagedList.Config config = new PagedList.Config.Builder() + .setPageSize(3) + .setPrefetchDistance(6) + .setEnablePlaceholders(true).build(); + + PagedList list = new PagedList.Builder<>(factory.create(), config).setFetchExecutor(Runnable::run).build(); + + Assert.assertEquals(100, list.size()); + + for(int index=0;index { + TestExtensions.assertThrowsException(IllegalArgumentException.class, (unused) -> { + Set simpleModel = SQLite.update(SimpleTestModels.SimpleModel.class).set(SimpleModel_Table.name.eq("name")); + QueryDataSource.toDataSourceFactory(simpleModel, db).create(); + return null; + }); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog2_Table.java b/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog2_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..1e8f600422d00f059b7cb0f3a74dbfb65a6929b3 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog2_Table.java @@ -0,0 +1,141 @@ +package com.dbflow5.prepackaged; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.prepackaged.PrepackagedDB.Dog2; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class Dog2_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Dog2.class, "id"); + + public static final Property breed = new Property(Dog2.class, "breed"); + + public static final Property color = new Property(Dog2.class, "color"); + + public static final Property newField = new Property(Dog2.class, "newField"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,breed,color,newField}; + + public Dog2_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Dog2.class; + } + + @Override + public final String getName() { + return "Dog"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "breed": { + return breed; + } + case "color": { + return color; + } + case "newField": { + return newField; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Dog2 model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getBreed()); + statement.bindStringOrNull(3, model.getColor()); + statement.bindStringOrNull(4, model.getNewField()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Dog2 model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getBreed()); + statement.bindStringOrNull(3, model.getColor()); + statement.bindStringOrNull(4, model.getNewField()); + statement.bindLong(5, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Dog2 model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Dog(id,breed,color,newField) VALUES (?,?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Dog(id,breed,color,newField) VALUES (?,?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Dog SET id=?,breed=?,color=?,newField=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Dog WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Dog(id INTEGER, breed TEXT, color TEXT, newField TEXT, PRIMARY KEY(id))"; + } + + @Override + public final Dog2 loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Dog2 model = new Dog2(0,null,null,null); + model.setId(cursor.getIntOrDefault("id")); + model.setBreed(cursor.getStringOrDefault("breed")); + model.setColor(cursor.getStringOrDefault("color")); + model.setNewField(cursor.getStringOrDefault("newField")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Dog2 model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog_Table.java b/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..dc1b69247d376fe1db25e4fd5b17348a373e37f5 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/prepackaged/Dog_Table.java @@ -0,0 +1,133 @@ +package com.dbflow5.prepackaged; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.prepackaged.PrepackagedDB.Dog; +import java.lang.IllegalArgumentException; +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; + +public final class Dog_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(Dog.class, "id"); + + public static final Property breed = new Property(Dog.class, "breed"); + + public static final Property color = new Property(Dog.class, "color"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,breed,color}; + + public Dog_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return Dog.class; + } + + @Override + public final String getName() { + return "Dog"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "breed": { + return breed; + } + case "color": { + return color; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, Dog model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getBreed()); + statement.bindStringOrNull(3, model.getColor()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, Dog model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getBreed()); + statement.bindStringOrNull(3, model.getColor()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, Dog model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO Dog(id,breed,color) VALUES (?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO Dog(id,breed,color) VALUES (?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE Dog SET id=?,breed=?,color=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM Dog WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS Dog(id INTEGER, breed TEXT, color TEXT, PRIMARY KEY(id))"; + } + + @Override + public final Dog loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + Dog model = new Dog(0, null, null); + model.setId(cursor.getIntOrDefault("id")); + model.setBreed(cursor.getStringOrDefault("breed")); + model.setColor(cursor.getStringOrDefault("color")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(Dog model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDB.java b/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDB.java new file mode 100644 index 0000000000000000000000000000000000000000..d7c0d6ca6b3d200c54bfb9449d5bbd6734657061 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDB.java @@ -0,0 +1,148 @@ +package com.dbflow5.prepackaged; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.Database; +import com.dbflow5.annotation.Migration; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.migration.AlterTableMigration; +import com.dbflow5.migration.BaseMigration; +import com.dbflow5.sql.SQLiteType; +import com.dbflow5.structure.BaseModel; + +import ohos.aafwk.ability.DataAbilityRemoteException; + +/** + * PrepackagedDB + * + * @author wangyin + * @since 2021/06/18 + */ + +@Database(version = 1) +public abstract class PrepackagedDB extends DBFlowDatabase{ + + @Database(version = 2) + public abstract static class MigratedPrepackagedDB extends DBFlowDatabase{ + @Migration(version = 2, database = MigratedPrepackagedDB.class, priority = 1) + public static class AddNewFieldMigration extends AlterTableMigration{ + public AddNewFieldMigration() { + super(Dog2.class); + } + @Override + public void onPreMigrate() { + addColumn(SQLiteType.TEXT, "newField", null); + } + } + @Migration(version = 2, database = MigratedPrepackagedDB.class, priority = 2) + static + public class AddSomeDataMigration extends BaseMigration{ + @Override + public void migrate(DatabaseWrapper database) throws DataAbilityRemoteException { + new Dog2("NewBreed","New Field Data").insert(database); + } + } + } + + @Table(database = PrepackagedDB.class, allFields = true) + public static class Dog extends BaseModel { + @PrimaryKey + public int id; + @Column + public String breed; + @Column + public String color; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public Dog(int id, String breed, String color) { + this.id = id; + this.breed = breed; + this.color = color; + } + + public Dog(String breed, String color) { + this.breed = breed; + this.color = color; + } + } + static class Dog2 extends BaseModel { + @PrimaryKey + public int id; + @Column + public String breed; + @Column + public String color; + @Column + public String newField; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getNewField() { + return newField; + } + + public void setNewField(String newField) { + this.newField = newField; + } + + public Dog2(int id, String breed, String color, String newField) { + this.id = id; + this.breed = breed; + this.color = color; + this.newField = newField; + } + + public Dog2(String breed, String newField) { + this.breed = breed; + this.newField = newField; + } + } + +} diff --git a/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.java b/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2b00687868719730c2afdcc2545320c0ed0827 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.java @@ -0,0 +1,61 @@ +package com.dbflow5.prepackaged; + +import com.dbflow5.DBFlowInstrumentedTestRule; +import com.dbflow5.DemoApp; +import com.dbflow5.config.DatabaseConfig; +import com.dbflow5.config.FlowConfig; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; + +import org.junit.Rule; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import com.dbflow5.prepackaged.PrepackagedDB; +import com.dbflow5.prepackaged.PrepackagedDB.Dog2; + +import java.util.List; + +/** + * PrepackagedDBTest + * + * @author wangyin + * @since 2021/06/18 + */ +public class PrepackagedDBTest { + @Rule + public DBFlowInstrumentedTestRule dblflowTestRule; + public PrepackagedDBTest() { + this.dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> { + DatabaseConfig.Builder builder1 = DatabaseConfig.builder(PrepackagedDB.class, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + builder1.databaseName("prepackaged"); + FlowConfig.builder(DemoApp.context) + .database(builder1.build()).build(); + + DatabaseConfig.Builder builder2 = DatabaseConfig.builder(PrepackagedDB.MigratedPrepackagedDB.class, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + builder1.databaseName("prepackaged_2"); + FlowConfig.builder(DemoApp.context) + .database(builder2.build()).build(); + return null; + }); + } + + @Test + public void assertWeCanLoadFromDB() { + PrepackagedDB db = FlowManager.getDatabase(PrepackagedDB.class); + List list = SQLite.select().from(Dog2.class).queryList(db); + assertFalse(list.isEmpty()); + } + + public void assertWeCanLoadFromDBPostMigrate(){ + PrepackagedDB.MigratedPrepackagedDB db = FlowManager.getDatabase(PrepackagedDB.MigratedPrepackagedDB.class); + List list = SQLite.select().from(Dog2.class).queryList(db); + assertFalse(list.isEmpty()); + Dog2 newData = SQLite.select().from(Dog2.class).where(Dog2_Table.breed.eq("NewBreed")).and(Dog2_Table.newField.eq("New Field Data")).querySingle(db); + assertNotNull(newData); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/ContentDatabase_Provider.java b/entry/src/ohosTest/java/com/dbflow5/provider/ContentDatabase_Provider.java new file mode 100644 index 0000000000000000000000000000000000000000..05f133a0dcab5d6058e042d00315fed125eea949 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/ContentDatabase_Provider.java @@ -0,0 +1,129 @@ +//package com.dbflow5.provider; +// +//import com.dbflow5.UriMatcher; +//import com.dbflow5.adapter.ModelAdapter; +//import com.dbflow5.annotation.ConflictAction; +//import com.dbflow5.config.FlowManager; +//import ohos.data.rdb.ValuesBucket; +//import ohos.data.resultset.ResultSet; +//import ohos.utils.net.Uri; +// +//import java.lang.IllegalArgumentException; +//import java.lang.Override; +//import java.lang.String; +//import javax.annotation.Generated; +// +///** +// * This is generated code. Please do not modify */ +//@Generated("com.dbflow5.processor.DBFlowProcessor") +//public final class ContentDatabase_Provider extends BaseContentProvider { +// private static final int ContentProviderModel_CONTENT_URI = 0; +// +// private final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); +// +// @Override +// public final boolean onCreate() { +// final String AUTHORITY = "com.grosner.content.test.ContentDatabase"; +// MATCHER.addURI(AUTHORITY, "ContentProviderModel", ContentProviderModel_CONTENT_URI); +// return super.onCreate(); +// } +// +// @Override +// public final String getDatabaseName() { +// return FlowManager.getDatabaseName(ContentDatabase.class); +// } +// +// @Override +// public final String getType(Uri uri) { +// String type = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// type = "vnd.android.cursor.dir/ContentProviderModel"; +// break; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// return type; +// } +// +// @Override +// public final ResultSet query(Uri uri, String[] projection, String selection, String[] selectionArgs, +// String sortOrder) { +// android.database.Cursor cursor = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("ContentProviderModel", projection, selection, selectionArgs, null, null, sortOrder); +// break; +// } +// } +// if (cursor != null) { +// cursor.setNotificationUri(getContext().getContentResolver(), uri); +// } +// return cursor; +// } +// +// @Override +// public final Uri insert(Uri uri, ContentValues values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// protected final int bulkInsert(Uri uri, ValuesBucket values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public final int delete(Uri uri, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("ContentProviderModel", selection, selectionArgs); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("ContentProviderModel", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +//} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..8e1827e9506bb4a630348baae48e5d149d52296d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderModel_Table.java @@ -0,0 +1,163 @@ +package com.dbflow5.provider; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.provider.ContentProviderObjects.ContentProviderModel; +import ohos.data.rdb.ValuesBucket; + +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class ContentProviderModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(ContentProviderModel.class, "id"); + + public static final Property notes = new Property(ContentProviderModel.class, "notes"); + + public static final Property title = new Property(ContentProviderModel.class, "title"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,notes,title}; + + public ContentProviderModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return ContentProviderModel.class; + } + + @Override + public final String getName() { + return "ContentProviderModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "notes": { + return notes; + } + case "title": { + return title; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(ContentProviderModel model, Number id) { + model.setId((int)id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, ContentProviderModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getNotes()); + statement.bindStringOrNull(3, model.getTitle()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, ContentProviderModel model) { + statement.bindLong(1, (long)model.getId()); + statement.bindStringOrNull(2, model.getNotes()); + statement.bindStringOrNull(3, model.getTitle()); + statement.bindLong(4, (long)model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, ContentProviderModel model) { + statement.bindLong(1, (long)model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO ContentProviderModel(id,notes,title) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO ContentProviderModel(id,notes,title) VALUES (nullif(?, 0),?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE ContentProviderModel SET id=?,notes=?,title=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM ContentProviderModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS ContentProviderModel(id INTEGER PRIMARY KEY AUTOINCREMENT, notes TEXT, title TEXT)"; + } + + @Override + public final ContentProviderModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + ContentProviderModel model = new ContentProviderModel(); + model.setId((int)cursor.getLongOrDefault("id", 0)); + model.setNotes(cursor.getStringOrDefault("notes")); + model.setTitle(cursor.getStringOrDefault("title")); + return model; + } + + @Override + public final boolean exists(ContentProviderModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(ContentProviderModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(ContentProviderModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq((long)model.getId())); + return clause; + } + + @Override + public final void bindToInsertValues(ValuesBucket values, ContentProviderModel model) { + values.putString("notes", model.getNotes()); + values.putString("title", model.getTitle()); + } + + @Override + public final void bindToContentValues(ValuesBucket values, ContentProviderModel model) { + values.putInteger("id", model.getId()); + bindToInsertValues(values, model); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderObjects.java b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderObjects.java new file mode 100644 index 0000000000000000000000000000000000000000..bd3351d381a5185d7651a22bea41a31d662cef45 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderObjects.java @@ -0,0 +1,225 @@ +package com.dbflow5.provider; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.Database; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.ForeignKeyReference; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.contentprovider.annotation.ContentProvider; +import com.dbflow5.contentprovider.annotation.TableEndpoint; + +import ohos.utils.net.Uri; + +import java.util.ArrayList; + +/** + * ContentProviderObjects + * + * @author wangyin + * @since 2021/06/19 + */ +public class ContentProviderObjects { + @ContentProvider(authority = ContentDatabase.AUTHORITY, database = ContentDatabase.class, + baseContentUri = ContentDatabase.BASE_CONTENT_URI) + @Database(version = ContentDatabase.VERSION) + public abstract static class ContentDatabase extends ContentProviderDatabase { + public static final String BASE_CONTENT_URI = "content://"; + public static final String AUTHORITY = "com.grosner.content.test.ContentDatabase"; + public static final int VERSION = 1; + } + + @TableEndpoint(name = ContentProviderModel.NAME, contentProvider = ContentDatabase.class) + public static class ContentProviderModel extends BaseProviderModel { + @PrimaryKey(autoincrement = true) + public int id; + @Column + public String notes; + @Column + public String title; + + public static final String NAME = "ContentProviderModel"; + + private Uri CONTENT_URI; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public ContentProviderModel() { + } + + public ContentProviderModel(int id, String notes, String title) { + this.id = id; + this.notes = notes; + this.title = title; + this.CONTENT_URI = ContentUtils.buildUriWithAuthority(ContentDatabase.AUTHORITY); + } + + @Override + public Uri deleteUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri insertUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri updateUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri queryUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + } + + @Table(database = ContentDatabase.class, generateContentValues = true) + public static class NoteModel extends BaseProviderModel { + @PrimaryKey(autoincrement = true) + public Long id; + @ForeignKey(references = {@ForeignKeyReference(columnName = "providerModel", foreignKeyColumnName = "id")}) + public ContentProviderModel contentProviderModel; + @Column + public String note; + @Column + public boolean isOpen; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ContentProviderModel getContentProviderModel() { + return contentProviderModel; + } + + public void setContentProviderModel(ContentProviderModel contentProviderModel) { + this.contentProviderModel = contentProviderModel; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public boolean isOpen() { + return isOpen; + } + + public void setOpen(boolean open) { + isOpen = open; + } + + public NoteModel(Long id, ContentProviderModel contentProviderModel, String note, boolean isOpen) { + this.id = id; + this.contentProviderModel = contentProviderModel; + this.note = note; + this.isOpen = isOpen; + } + + public NoteModel() { + } + + @Override + public Uri deleteUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri insertUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri updateUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + + @Override + public Uri queryUri() { + return TestContentProvider.ContentProviderModel.getCONTENT_URI(); + } + } + + @Table(database = ContentDatabase.class, generateContentValues = true) + public static class TestSyncableModel extends BaseSyncableProviderModel { + @PrimaryKey(autoincrement = true) + public Long id; + @Column + public String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TestSyncableModel(Long id, String name) { + this.id = id; + this.name = name; + } + + public TestSyncableModel() { + } + + @Override + public Uri deleteUri() { + return TestContentProvider.TestSyncableModel.getCONTENT_URI(); + } + + @Override + public Uri insertUri() { + return TestContentProvider.TestSyncableModel.getCONTENT_URI(); + } + + @Override + public Uri updateUri() { + return TestContentProvider.TestSyncableModel.getCONTENT_URI(); + } + + @Override + public Uri queryUri() { + return TestContentProvider.TestSyncableModel.getCONTENT_URI(); + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderTests.java b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderTests.java new file mode 100644 index 0000000000000000000000000000000000000000..9fdff2401716fad84df8d031b81e484163434a3f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/ContentProviderTests.java @@ -0,0 +1,173 @@ +package com.dbflow5.provider; + +import com.dbflow5.DBFlowInstrumentedTestRule; +import com.dbflow5.DemoApp; +import com.dbflow5.config.DatabaseConfig; +import com.dbflow5.config.FlowConfig; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.query.SQLite; + +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; + +import static com.dbflow5.query.SQLite.delete; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * ContentProviderTests + * + * @author wangyin + * @since 2021/06/19 + */ +public class ContentProviderTests { + public DBFlowInstrumentedTestRule dblflowTestRule; +// public ProviderTestRule contentProviderRule; + private DataAbilityHelper mockContentResolver; + + public ContentProviderTests() { + this.dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> { + DatabaseConfig.Builder builder1 = DatabaseConfig.builder(ContentProviderObjects.ContentDatabase.class, OhosSQLiteOpenHelper.createHelperCreator(AbilityDelegatorRegistry.getAbilityDelegator().getAppContext())); + builder1.databaseName("content"); + FlowConfig.builder(DemoApp.context) + .database(builder1.build()).build(); + return null; + }); +// this.contentProviderRule = new ProviderTestRule.Builder(TestContentProvider_Provider.class, TestContentProvider.AUTHORITY).build(); + this.mockContentResolver = FlowManager.globalContentResolver; + } + + @Before + public void overrideContentResolver() { + FlowManager.globalContentResolver = mockContentResolver; + } + + @Test + public void testContentProviderUtils() throws DataAbilityRemoteException { + ContentProviderObjects.ContentDatabase db = FlowManager.getDatabase(ContentProviderObjects.ContentDatabase.class); + new ArrayList() { + { + add(ContentProviderObjects.NoteModel.class); + add(ContentProviderObjects.ContentProviderModel.class); + } + }.forEach(aClass -> delete(aClass).executeUpdateDelete(db)); + ContentProviderObjects.ContentProviderModel contentProviderModel = new ContentProviderObjects.ContentProviderModel(); + contentProviderModel.notes = "Test"; + int id = ContentUtils.insert(mockContentResolver, TestContentProvider.ContentProviderModel.getCONTENT_URI(), contentProviderModel); + assertEquals(TestContentProvider.ContentProviderModel.getCONTENT_URI().toString() + "/" + contentProviderModel.id, String.valueOf(id)); + assertTrue(contentProviderModel.exists(db)); + + contentProviderModel.notes = "NewTest"; + int update = ContentUtils.update(mockContentResolver, TestContentProvider.ContentProviderModel.getCONTENT_URI(), contentProviderModel); + assertEquals(update, 1); + assertTrue(contentProviderModel.exists(db)); + contentProviderModel = contentProviderModel.load(db); + assert contentProviderModel != null; + assertEquals("NewTest", contentProviderModel.notes); + + ContentProviderObjects.NoteModel noteModel = new ContentProviderObjects.NoteModel(); + noteModel.note = "Test"; + noteModel.contentProviderModel = contentProviderModel; + id = ContentUtils.insert(mockContentResolver, TestContentProvider.ContentProviderModel.getCONTENT_URI(), noteModel); + assertEquals(TestContentProvider.NoteModel.getCONTENT_URI().toString() + "/" + noteModel.id, String.valueOf(id)); + assertTrue(noteModel.exists(db)); + + assertTrue(ContentUtils.delete(mockContentResolver, TestContentProvider.NoteModel.getCONTENT_URI(), noteModel) > 0); + assertFalse(noteModel.exists(db)); + + assertTrue(ContentUtils.delete(mockContentResolver, TestContentProvider.ContentProviderModel.getCONTENT_URI(), contentProviderModel) > 0); + assertFalse(contentProviderModel.exists(db)); + + new ArrayList() { + { + add(ContentProviderObjects.NoteModel.class); + add(ContentProviderObjects.ContentProviderModel.class); + } + }.forEach(aClass -> delete(aClass).executeUpdateDelete(db)); + } + + @Test + public void testContentProviderNative() throws DataAbilityRemoteException { + ContentProviderObjects.ContentDatabase db = FlowManager.getDatabase(ContentProviderObjects.ContentDatabase.class); + new ArrayList() { + { + add(ContentProviderObjects.NoteModel.class); + add(ContentProviderObjects.ContentProviderModel.class); + } + }.forEach(aClass -> delete(aClass).executeUpdateDelete(db)); + ContentProviderObjects.ContentProviderModel contentProviderModel = new ContentProviderObjects.ContentProviderModel(); + contentProviderModel.notes = "Test"; + contentProviderModel.insert(db); + assertTrue(contentProviderModel.exists(db)); + + contentProviderModel.notes = "NewTest"; + contentProviderModel.update(db); + contentProviderModel = contentProviderModel.load(db); + assert contentProviderModel != null; + assertEquals("NewTest", contentProviderModel.notes); + + ContentProviderObjects.NoteModel noteModel = new ContentProviderObjects.NoteModel(); + noteModel.note = "Test"; + noteModel.contentProviderModel = contentProviderModel; + noteModel.insert(db); + + noteModel.note = "NewTest"; + noteModel.update(db); + noteModel = noteModel.load(db); + assert noteModel != null; + assertEquals("NewTest", noteModel.note); + + assertTrue(noteModel.exists(db)); + + noteModel.delete(db); + assertFalse(noteModel.exists(db)); + + contentProviderModel.delete(db); + assertFalse(contentProviderModel.exists(db)); + + new ArrayList() { + { + add(ContentProviderObjects.NoteModel.class); + add(ContentProviderObjects.ContentProviderModel.class); + } + }.forEach(aClass -> delete(aClass).executeUpdateDelete(db)); + } + + @Test + public void testSyncableModel() throws DataAbilityRemoteException { + ContentProviderObjects.ContentDatabase db = FlowManager.getDatabase(ContentProviderObjects.ContentDatabase.class); + delete(ContentProviderObjects.TestSyncableModel.class).execute(db); + ContentProviderObjects.TestSyncableModel testSyncableModel= new ContentProviderObjects.TestSyncableModel(); + testSyncableModel.name="Name"; + testSyncableModel.save(db); + + assertTrue(testSyncableModel.exists(db)); + + testSyncableModel.name = "TestName"; + testSyncableModel.update(db); + assertEquals(testSyncableModel.name, "TestName"); + + testSyncableModel =SQLite.select().from(ContentProviderObjects.TestSyncableModel.class).where().querySingle(db); + + assert testSyncableModel != null; + ContentProviderObjects.TestSyncableModel fromContentProvider = new ContentProviderObjects.TestSyncableModel(); + fromContentProvider.id = testSyncableModel.id; + fromContentProvider = fromContentProvider.load(db); + + assert fromContentProvider != null; + assertEquals(fromContentProvider.name, testSyncableModel.name); + assertEquals(fromContentProvider.id, testSyncableModel.id); + + testSyncableModel.delete(db); + assertFalse(testSyncableModel.exists(db)); + + delete(ContentProviderObjects.TestSyncableModel.class).executeUpdateDelete(db); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/NoteModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/provider/NoteModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..19807e7b3c8462b2d60d9cc9b1edf0aa4076900c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/NoteModel_Table.java @@ -0,0 +1,203 @@ +package com.dbflow5.provider; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Boolean; +import com.dbflow5.provider.ContentProviderObjects.NoteModel; +import ohos.data.rdb.ValuesBucket; + +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; +import javax.annotation.Generated; + +/** + * This is generated code. Please do not modify */ +@Generated("com.dbflow5.processor.DBFlowProcessor") +public final class NoteModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(NoteModel.class, "id"); + + /** + * Foreign Key */ + public static final Property providerModel = new Property(NoteModel.class, "providerModel"); + + public static final Property note = new Property(NoteModel.class, "note"); + + public static final Property isOpen = new Property(NoteModel.class, "isOpen"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,providerModel,note,isOpen}; + + public NoteModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return NoteModel.class; + } + + @Override + public final String getName() { + return "`NoteModel`"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "`id`": { + return id; + } + case "`providerModel`": { + return providerModel; + } + case "`note`": { + return note; + } + case "`isOpen`": { + return isOpen; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(NoteModel model, Number id) { + model.setId(id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, NoteModel model) { + statement.bindLong(1, model.getId()); + if (model.getContentProviderModel() != null) { + statement.bindLong(2, (long)model.getContentProviderModel().getId()); + } else { + statement.bindNull(2); + } + statement.bindStringOrNull(3, model.getNote()); + statement.bindLong(4, model.isOpen() ? 1L : 0L); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, NoteModel model) { + statement.bindLong(1, model.getId()); + if (model.getContentProviderModel() != null) { + statement.bindLong(2, (long)model.getContentProviderModel().getId()); + } else { + statement.bindNull(2); + } + statement.bindStringOrNull(3, model.getNote()); + statement.bindLong(4, model.isOpen() ? 1L : 0L); + statement.bindLong(5, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, NoteModel model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO `NoteModel`(`id`,`providerModel`,`note`,`isOpen`) VALUES (nullif(?, 0),?,?,?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO `NoteModel`(`id`,`providerModel`,`note`,`isOpen`) VALUES (nullif(?, 0),?,?,?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE `NoteModel` SET `id`=?,`providerModel`=?,`note`=?,`isOpen`=? WHERE `id`=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM `NoteModel` WHERE `id`=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS `NoteModel`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `providerModel` INTEGER, `note` TEXT, `isOpen` INTEGER, FOREIGN KEY(`providerModel`) REFERENCES ContentProviderModel (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION)"; + } + + @Override + public final NoteModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + NoteModel model = new NoteModel(); + model.setId(cursor.getLongOrDefault("id")); + int index_providerModel_ContentProviderModel_Table = cursor.getColumnIndexForName("providerModel"); + if (index_providerModel_ContentProviderModel_Table != -1 && !cursor.isColumnNull(index_providerModel_ContentProviderModel_Table)) { + model.setContentProviderModel(SQLite.select().from(ContentProviderObjects.ContentProviderModel.class).where() + .and(ContentProviderModel_Table.id.eq(cursor.getLong(index_providerModel_ContentProviderModel_Table))) + .querySingle(wrapper)); + } else { + model.setContentProviderModel(null); + } + model.setNote(cursor.getStringOrDefault("note")); + int index_isOpen = cursor.getColumnIndexForName("isOpen"); + if (index_isOpen != -1 && !cursor.isColumnNull(index_isOpen)) { + model.setOpen(cursor.getBoolean(index_isOpen)); + } else { + model.setOpen(false); + } + return model; + } + + @Override + public final boolean exists(NoteModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(NoteModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(NoteModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } + + @Override + public final void bindToInsertValues(ValuesBucket values, NoteModel model) { + if (model.getContentProviderModel() != null) { + values.putInteger("`providerModel`", model.getContentProviderModel().getId()); + } else { + values.putNull("`providerModel`"); + } + values.putString("`note`", model.getNote()); + values.putInteger("`isOpen`", model.isOpen() ? 1 : 0); + } + + @Override + public final void bindToContentValues(ValuesBucket values, NoteModel model) { + values.putLong("`id`", model.getId()); + bindToInsertValues(values, model); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider.java b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..3eda11ebe5086a83756a70b4cbee934e6eaa1737 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider.java @@ -0,0 +1,166 @@ +package com.dbflow5.provider; + +import com.dbflow5.contentprovider.annotation.ContentProvider; +import com.dbflow5.contentprovider.annotation.ContentType; +import com.dbflow5.contentprovider.annotation.ContentUri; +import com.dbflow5.contentprovider.annotation.Notify; +import com.dbflow5.contentprovider.annotation.NotifyMethod; +import com.dbflow5.contentprovider.annotation.PathSegment; +import com.dbflow5.contentprovider.annotation.TableEndpoint; +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.app.Context; +import ohos.data.rdb.ValuesBucket; +import ohos.data.resultset.ResultSet; +import ohos.utils.net.Uri; + +import static com.dbflow5.SqlUtils.getContentValuesKey; + +/** + * TestContentProvider + * + * @author wangyin + * @since 2021/06/19 + */ +class TestContentProvider2{ + @TableEndpoint(name = ContentProviderModel.ENDPOINT, + contentProvider = ContentProviderObjects.ContentDatabase.class) + static class ContentProviderModel{ + public static final String ENDPOINT = "ContentProviderModel"; + + @ContentUri(path = TestContentProvider.ContentProviderModel.ENDPOINT, + type = ContentType.VND_MULTIPLE + TestContentProvider.ContentProviderModel.ENDPOINT) + public static Uri CONTENT_URI = TestContentProvider.buildUri(TestContentProvider.ContentProviderModel.ENDPOINT); + + @ContentUri(path = TestContentProvider.ContentProviderModel.ENDPOINT + "/#", + type = ContentType.VND_SINGLE + TestContentProvider.ContentProviderModel.ENDPOINT, + segments = {@PathSegment(segment = 1, column = "id")}) + public static Uri withId(long id) { + return TestContentProvider.buildUri(String.valueOf(id)); + } + + @Notify(notifyMethod = NotifyMethod.INSERT, paths = {TestContentProvider.ContentProviderModel.ENDPOINT + "/#"}) + public static Uri[] onInsert(ValuesBucket contentValues) { + Long id = contentValues.getLong("id"); + return new Uri[]{withId(id)}; + } + } +} +@ContentProvider(authority = TestContentProvider.AUTHORITY, database = ContentProviderObjects.ContentDatabase.class, + baseContentUri = TestContentProvider.BASE_CONTENT_URI) +public class TestContentProvider { + + public static final String AUTHORITY = "com.dbflow5.test.provider"; + public static final String BASE_CONTENT_URI = "content://"; + + public static Uri buildUri(String... paths) { + Uri.Builder builder = Uri.parse(BASE_CONTENT_URI + AUTHORITY).makeBuilder(); + for (String path : paths) { + builder.appendDecodedPath(path); + } + return builder.build(); + } + + @TableEndpoint(name = ContentProviderModel.ENDPOINT, contentProvider = ContentProviderObjects.ContentDatabase.class) + static class ContentProviderModel { + public static final String ENDPOINT = "ContentProviderModel"; + + @ContentUri(path = ENDPOINT + "/#", + type = ContentType.VND_SINGLE + ENDPOINT, + segments = {@PathSegment(segment = 1, column = "id")}) + public static Uri withId(long id) { + return buildUri(String.valueOf(id)); + } + + @Notify(notifyMethod = NotifyMethod.INSERT, paths = {ENDPOINT + "/#"}) + public static Uri[] onInsert(ValuesBucket contentValues) { + Long id = contentValues.getLong("id"); + return new Uri[]{withId(id)}; + } + + @ContentUri(path = ENDPOINT, + type = ContentType.VND_MULTIPLE + ENDPOINT) + public static Uri getCONTENT_URI() { + return buildUri(ENDPOINT); + } + } + + @TableEndpoint(name = NoteModel.ENDPOINT, contentProvider = ContentProviderObjects.ContentDatabase.class) + static class NoteModel { + public static final String ENDPOINT = "NoteModel"; + + @ContentUri(path = ENDPOINT, type = ContentType.VND_MULTIPLE + ENDPOINT) + public static Uri getCONTENT_URI() { + return buildUri(ENDPOINT); + } + @ContentUri(path = ENDPOINT + "/#", + type = ContentType.VND_MULTIPLE + ENDPOINT, + segments = {@PathSegment(segment = 1, column = "id")}) + public static Uri withId(long id) { + return buildUri(ENDPOINT, String.valueOf(id)); + } + + @ContentUri(path = ENDPOINT + "/#/#", + type = ContentType.VND_SINGLE + ContentProviderModel.ENDPOINT, + segments = {@PathSegment(segment = 2, column = "id"), @PathSegment(segment = 2, column = "isOpen")}) + public static Uri fromList(long id) { + return buildUri(ENDPOINT, "fromList", String.valueOf(id)); + } + + @ContentUri(path = ENDPOINT + "/#/#", + type = ContentType.VND_SINGLE + ContentProviderModel.ENDPOINT, + segments = {@PathSegment(segment = 1, column = "id"), @PathSegment(segment = 2, column = "isOpen")}) + public static Uri withOpenId(long id, boolean isOpen) { + return buildUri(ENDPOINT, String.valueOf(id), String.valueOf(isOpen)); + } + + + @Notify(notifyMethod = NotifyMethod.INSERT, paths = {ENDPOINT}) + public static Uri[] onInsert(ValuesBucket contentValues) { + Long listId = contentValues.getLong(getContentValuesKey(contentValues, "providerModel")); + return new Uri[]{ContentProviderModel.withId(listId), fromList(listId)}; + } + + @Notify(notifyMethod = NotifyMethod.INSERT, paths = {ENDPOINT}) + public static Uri onInsert2(ValuesBucket contentValues) { + Long listId = contentValues.getLong(getContentValuesKey(contentValues, "providerModel")); + return fromList(listId); + } + + @Notify(notifyMethod = NotifyMethod.UPDATE, paths = {ENDPOINT + "/#"}) + public static Uri[] onUpdate(Context context, Uri uri) throws DataAbilityRemoteException { + + long noteId = Long.parseLong(uri.getDecodedPathList().get(1)); + ResultSet c = DataAbilityHelper.creator(context).query(uri, new String[]{"noteModel"}, null); + assert c != null; + c.goToFirstRow(); + + long listId = c.getLong(c.getColumnIndexForName("providerModel")); + c.close(); + return new Uri[]{withId(noteId), fromList(listId), ContentProviderModel.withId(listId)}; + } + + @Notify(notifyMethod = NotifyMethod.DELETE, paths = {ENDPOINT + "/#"}) + public static Uri[] onDelete(Context context, Uri uri) throws DataAbilityRemoteException { + + long noteId = Long.parseLong(uri.getDecodedPathList().get(1)); + ResultSet c = DataAbilityHelper.creator(context).query(uri, new String[]{"noteModel"}, null); + assert c != null; + c.goToFirstRow(); + + long listId = c.getLong(c.getColumnIndexForName("providerModel")); + c.close(); + return new Uri[]{withId(noteId), fromList(listId), ContentProviderModel.withId(listId)}; + } + } + + @TableEndpoint(name = TestSyncableModel.ENDPOINT, contentProvider = ContentProviderObjects.ContentDatabase.class) + static class TestSyncableModel { + public static final String ENDPOINT = "TestSyncableModel"; + + @ContentUri(path = ENDPOINT, type = ContentType.VND_MULTIPLE + ENDPOINT) + public static Uri getCONTENT_URI() { + return buildUri(ENDPOINT); + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider2_Provider.java b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider2_Provider.java new file mode 100644 index 0000000000000000000000000000000000000000..a3c5d920d726d3247d871110fb7f205934137dca --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider2_Provider.java @@ -0,0 +1,176 @@ +//package com.dbflow5.provider; +// +//import com.dbflow5.UriMatcher; +//import com.dbflow5.adapter.ModelAdapter; +//import com.dbflow5.annotation.ConflictAction; +//import com.dbflow5.config.FlowManager; +//import ohos.data.rdb.ValuesBucket; +//import ohos.utils.net.Uri; +// +//import java.lang.IllegalArgumentException; +//import java.lang.Override; +//import java.lang.String; +//import java.util.List; +//import javax.annotation.Generated; +// +///** +// * This is generated code. Please do not modify */ +//@Generated("com.dbflow5.processor.DBFlowProcessor") +//public final class TestContentProvider2_Provider extends BaseContentProvider { +// private static final int ContentProviderModel_CONTENT_URI = 0; +// +// private static final int ContentProviderModel_withId = 1; +// +// private final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); +// +// @Override +// public final boolean onCreate() { +// final String AUTHORITY = "com.dbflow5.test.provider"; +// MATCHER.addURI(AUTHORITY, "ContentProviderModel", ContentProviderModel_CONTENT_URI); +// MATCHER.addURI(AUTHORITY, "ContentProviderModel/#", ContentProviderModel_withId); +// return super.onCreate(); +// } +// +// @Override +// public final String getDatabaseName() { +// return FlowManager.getDatabaseName(ContentDatabase.class); +// } +// +// @Override +// public final String getType(Uri uri) { +// String type = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// type = "vnd.android.cursor.dir/ContentProviderModel"; +// break; +// } +// case ContentProviderModel_withId: { +// type = "vnd.android.cursor.item/ContentProviderModel"; +// break; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// return type; +// } +// +// @Override +// public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, +// String sortOrder) { +// Cursor cursor = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("ContentProviderModel", projection, selection, selectionArgs, null, null, sortOrder); +// break; +// } +// case ContentProviderModel_withId: { +// List segments = uri.getPathSegments(); +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("ContentProviderModel", projection, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), null, null, sortOrder); +// break; +// } +// } +// if (cursor != null) { +// cursor.setNotificationUri(getContext().getContentResolver(), uri); +// } +// return cursor; +// } +// +// @Override +// public final Uri insert(Uri uri, ValuesBucket values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider2.ContentProviderModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return ContentUris.withAppendedId(uri, id); +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// protected final int bulkInsert(Uri uri, ValuesBucket values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider2.ContentProviderModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return id > 0 ? 1 : 0; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public final int delete(Uri uri, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("ContentProviderModel", selection, selectionArgs); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case ContentProviderModel_withId: { +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("ContentProviderModel", DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)})); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public int update(Uri uri, ValuesBucket values, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("ContentProviderModel", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("ContentProviderModel", values, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +//} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider_Provider.java b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider_Provider.java new file mode 100644 index 0000000000000000000000000000000000000000..6aa3e1b56bcac8a7093ef3ff2c2d5ab49fd2878f --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/TestContentProvider_Provider.java @@ -0,0 +1,386 @@ +//package com.dbflow5.provider; +// +//import com.dbflow5.UriMatcher; +//import com.dbflow5.adapter.ModelAdapter; +//import com.dbflow5.annotation.ConflictAction; +//import com.dbflow5.config.FlowManager; +//import ohos.utils.net.Uri; +// +//import java.lang.IllegalArgumentException; +//import java.lang.Override; +//import java.lang.String; +//import java.util.List; +//import javax.annotation.Generated; +// +///** +// * This is generated code. Please do not modify */ +//@Generated("com.dbflow5.processor.DBFlowProcessor") +//public final class TestContentProvider_Provider extends BaseContentProvider { +// private static final int ContentProviderModel_CONTENT_URI = 0; +// +// private static final int ContentProviderModel_withId = 1; +// +// private static final int NoteModel_CONTENT_URI = 2; +// +// private static final int NoteModel_withId = 3; +// +// private static final int NoteModel_fromList = 4; +// +// private static final int NoteModel_withOpenId = 5; +// +// private static final int TestSyncableModel_CONTENT_URI = 6; +// +// private final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); +// +// @Override +// public final boolean onCreate() { +// final String AUTHORITY = "com.dbflow5.test.provider"; +// MATCHER.addURI(AUTHORITY, "ContentProviderModel", ContentProviderModel_CONTENT_URI); +// MATCHER.addURI(AUTHORITY, "ContentProviderModel/#", ContentProviderModel_withId); +// MATCHER.addURI(AUTHORITY, "NoteModel", NoteModel_CONTENT_URI); +// MATCHER.addURI(AUTHORITY, "NoteModel/#", NoteModel_withId); +// MATCHER.addURI(AUTHORITY, "NoteModel/#/#", NoteModel_fromList); +// MATCHER.addURI(AUTHORITY, "NoteModel/#/#", NoteModel_withOpenId); +// MATCHER.addURI(AUTHORITY, "TestSyncableModel", TestSyncableModel_CONTENT_URI); +// return super.onCreate(); +// } +// +// @Override +// public final String getDatabaseName() { +// return FlowManager.getDatabaseName(ContentDatabase.class); +// } +// +// @Override +// public final String getType(Uri uri) { +// String type = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// type = "vnd.android.cursor.dir/ContentProviderModel"; +// break; +// } +// case ContentProviderModel_withId: { +// type = "vnd.android.cursor.item/ContentProviderModel"; +// break; +// } +// case NoteModel_CONTENT_URI: { +// type = "vnd.android.cursor.dir/NoteModel"; +// break; +// } +// case NoteModel_withId: { +// type = "vnd.android.cursor.dir/NoteModel"; +// break; +// } +// case NoteModel_fromList: { +// type = "vnd.android.cursor.item/ContentProviderModel"; +// break; +// } +// case NoteModel_withOpenId: { +// type = "vnd.android.cursor.item/ContentProviderModel"; +// break; +// } +// case TestSyncableModel_CONTENT_URI: { +// type = "vnd.android.cursor.dir/TestSyncableModel"; +// break; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// return type; +// } +// +// @Override +// public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, +// String sortOrder) { +// android.database.Cursor cursor = null; +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("ContentProviderModel", projection, selection, selectionArgs, null, null, sortOrder); +// break; +// } +// case ContentProviderModel_withId: { +// List segments = uri.getPathSegments(); +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("ContentProviderModel", projection, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), null, null, sortOrder); +// break; +// } +// case NoteModel_CONTENT_URI: { +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("NoteModel", projection, selection, selectionArgs, null, null, sortOrder); +// break; +// } +// case NoteModel_withId: { +// List segments = uri.getPathSegments(); +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("NoteModel", projection, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), null, null, sortOrder); +// break; +// } +// case NoteModel_fromList: { +// List segments = uri.getPathSegments(); +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("NoteModel", projection, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(2)}), null, null, sortOrder); +// break; +// } +// case NoteModel_withOpenId: { +// List segments = uri.getPathSegments(); +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("NoteModel", projection, DatabaseUtils.concatenateWhere(selection, "id = ? AND isOpen = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1), segments.get(2)}), null, null, sortOrder); +// break; +// } +// case TestSyncableModel_CONTENT_URI: { +// cursor = FlowManager.getDatabase(ContentDatabase.class).query("TestSyncableModel", projection, selection, selectionArgs, null, null, sortOrder); +// break; +// } +// } +// if (cursor != null) { +// cursor.setNotificationUri(getContext().getContentResolver(), uri); +// } +// return cursor; +// } +// +// @Override +// public final Uri insert(Uri uri, ContentValues values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider.ContentProviderModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return ContentUris.withAppendedId(uri, id); +// } +// case NoteModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider.NoteModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// Uri notifyUrionInsert2 = TestContentProvider.NoteModel.onInsert2(values); +// getContext().getContentResolver().notifyChange(notifyUrionInsert2, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case NoteModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case NoteModel_fromList: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case NoteModel_withOpenId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// case TestSyncableModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "TestSyncableModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("TestSyncableModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return ContentUris.withAppendedId(uri, id); +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// protected final int bulkInsert(Uri uri, ContentValues values) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("ContentProviderModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider.ContentProviderModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return id > 0 ? 1 : 0; +// } +// case NoteModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// Uri[] notifyUrisonInsert = TestContentProvider.NoteModel.onInsert(values); +// for (Uri notifyUri: notifyUrisonInsert) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// Uri notifyUrionInsert2 = TestContentProvider.NoteModel.onInsert2(values); +// getContext().getContentResolver().notifyChange(notifyUrionInsert2, null); +// return id > 0 ? 1 : 0; +// } +// case NoteModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// case NoteModel_fromList: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// case NoteModel_withOpenId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("NoteModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// case TestSyncableModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "TestSyncableModel")); +// final long id = FlowManager.getDatabase(ContentDatabase.class).insertWithOnConflict("TestSyncableModel", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction())); +// getContext().getContentResolver().notifyChange(uri, null); +// return id > 0 ? 1 : 0; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public final int delete(Uri uri, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("ContentProviderModel", selection, selectionArgs); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case ContentProviderModel_withId: { +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("ContentProviderModel", DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)})); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_CONTENT_URI: { +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("NoteModel", selection, selectionArgs); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_withId: { +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("NoteModel", DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)})); +// Uri[] notifyUrisonDelete = TestContentProvider.NoteModel.onDelete(getContext(), uri); +// for (Uri notifyUri: notifyUrisonDelete) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return (int) count; +// } +// case NoteModel_fromList: { +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("NoteModel", DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(2)})); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_withOpenId: { +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("NoteModel", DatabaseUtils.concatenateWhere(selection, "id = ? AND isOpen = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1), segments.get(2)})); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case TestSyncableModel_CONTENT_URI: { +// long count = FlowManager.getDatabase(ContentDatabase.class).delete("TestSyncableModel", selection, selectionArgs); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +// +// @Override +// public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { +// switch(MATCHER.match(uri)) { +// case ContentProviderModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("ContentProviderModel", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case ContentProviderModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "ContentProviderModel")); +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("ContentProviderModel", values, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("NoteModel", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_withId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("NoteModel", values, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1)}), ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// Uri[] notifyUrisonUpdate = TestContentProvider.NoteModel.onUpdate(getContext(), uri); +// for (Uri notifyUri: notifyUrisonUpdate) { +// getContext().getContentResolver().notifyChange(notifyUri, null); +// } +// return (int) count; +// } +// case NoteModel_fromList: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("NoteModel", values, DatabaseUtils.concatenateWhere(selection, "id = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(2)}), ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case NoteModel_withOpenId: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "NoteModel")); +// List segments = uri.getPathSegments(); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("NoteModel", values, DatabaseUtils.concatenateWhere(selection, "id = ? AND isOpen = ?"), DatabaseUtils.appendSelectionArgs(selectionArgs, new String[] {segments.get(1), segments.get(2)}), ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// case TestSyncableModel_CONTENT_URI: { +// ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName(ContentDatabase.class, "TestSyncableModel")); +// long count = FlowManager.getDatabase(ContentDatabase.class).updateWithOnConflict("TestSyncableModel", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction())); +// if (count > 0) { +// getContext().getContentResolver().notifyChange(uri, null); +// } +// return (int) count; +// } +// default: { +// throw new IllegalArgumentException("Unknown URI" + uri); +// } +// } +// } +//} diff --git a/entry/src/ohosTest/java/com/dbflow5/provider/TestSyncableModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/provider/TestSyncableModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..7697b9617946d75d05c3ea3b816b12e7cf2ab212 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/provider/TestSyncableModel_Table.java @@ -0,0 +1,154 @@ +package com.dbflow5.provider; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.provider.ContentProviderObjects.TestSyncableModel; +import ohos.data.rdb.ValuesBucket; + +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class TestSyncableModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(TestSyncableModel.class, "id"); + + public static final Property name = new Property(TestSyncableModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public TestSyncableModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return TestSyncableModel.class; + } + + @Override + public final String getName() { + return "TestSyncableModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(TestSyncableModel model, Number id) { + model.setId(id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, TestSyncableModel model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, TestSyncableModel model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + statement.bindLong(3, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, TestSyncableModel model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO TestSyncableModel(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO TestSyncableModel(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE TestSyncableModel SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM TestSyncableModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS TestSyncableModel(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final TestSyncableModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + TestSyncableModel model = new TestSyncableModel(); + model.setId(cursor.getLongOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final boolean exists(TestSyncableModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(TestSyncableModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(TestSyncableModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } + + @Override + public final void bindToInsertValues(ValuesBucket values, TestSyncableModel model) { + values.putString("name", model.getName()); + } + + @Override + public final void bindToContentValues(ValuesBucket values, TestSyncableModel model) { + values.putLong("id", model.getId()); + bindToInsertValues(values, model); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/query/cache/ModelLruCacheTest.java b/entry/src/ohosTest/java/com/dbflow5/query/cache/ModelLruCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..79f8aa859a60739cd4e6e5d6a41a74c5aea8b68d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/query/cache/ModelLruCacheTest.java @@ -0,0 +1,43 @@ +package com.dbflow5.query.cache; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels.NumberModel; + +import org.junit.Assert; +import org.junit.Test; + +/** + * ModelLruCacheTest + * + * @author wangyin + * @since 2021/06/19 + */ +public class ModelLruCacheTest extends BaseUnitTest { + + @Test + public void validateCacheAddRemove(){ + + SimpleMapCache cache = new SimpleMapCache<>(10); + cache.addModel(1,new NumberModel(1)); + + Assert.assertEquals(1, cache.get(1).id); + Assert.assertEquals(1, cache.cache.size()); + + cache.removeModel(1); + + Assert.assertTrue(cache.cache.isEmpty()); + + } + @Test + public void validateCacheClear(){ + SimpleMapCache cache = new SimpleMapCache<>(10); + cache.addModel(1,new NumberModel(1)); + cache.addModel(2,new NumberModel(2)); + + Assert.assertEquals(2, cache.cache.size()); + + cache.clear(); + + Assert.assertTrue(cache.cache.isEmpty()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.java b/entry/src/ohosTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5ff12e0de9bd1a2a788a4d675091d8aac692b6f0 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.java @@ -0,0 +1,45 @@ +package com.dbflow5.query.cache; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels.SimpleModel; + +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * SimpleMapCacheTest + * + * @author wangyin + * @since 2021/06/19 + */ +public class SimpleMapCacheTest extends BaseUnitTest { + + @Test + public void validateCacheAddRemove(){ + + SimpleMapCache cache = new SimpleMapCache<>(10); + cache.addModel("1",new SimpleModel("1")); + + assertEquals("1", cache.get("1").name); + assertEquals(1, cache.cache.size()); + + cache.removeModel("1"); + + Assert.assertTrue(cache.cache.isEmpty()); + + } + @Test + public void validateCacheClear(){ + SimpleMapCache cache = new SimpleMapCache<>(10); + cache.addModel("1",new SimpleModel("1")); + cache.addModel("2",new SimpleModel("2")); + + assertEquals(2, cache.cache.size()); + + cache.clear(); + + Assert.assertTrue(cache.cache.isEmpty()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.java b/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fb03ec0a934c199386c1229a669b915c277b6143 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.java @@ -0,0 +1,103 @@ +package com.dbflow5.query.list; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * FlowCursorIteratorTest + * + * @author wangyin + * @since 2021/06/19 + */ +public class FlowCursorIteratorTest extends BaseUnitTest { + + @Test + public void testCanIterateFullList() { + final int[] count = {0}; + + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + + db.beginTransactionAsync((Function) databaseWrapper -> { + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + return null; + }).success((objectTransaction, o) -> { + FlowCursorIterator iterator = SQLite.select().from(SimpleModel.class).cursorList(db).iterator(); + assertFalse(iterator.isClosed()); + + try { + iterator.forEachRemaining(simpleModel -> { + assertEquals(String.valueOf(count[0]), simpleModel.name); + count[0]++; + }); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + iterator.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + assertTrue(iterator.isClosed()); + return null; + }).execute(null, null, null, null); + } + + @Test + public void testCanIteratePartialList() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + + db.beginTransactionAsync((Function) databaseWrapper -> { + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + return null; + }).success((objectTransaction, o) -> { + FlowCursorIterator iterator = SQLite.select().from(SimpleModel.class).cursorList(db).iterator(2, 7); + final int[] count = {0}; + + iterator.forEachRemaining(simpleModel -> { + assertEquals(String.valueOf(count[0] + 2), simpleModel.name); + count[0]++; + }); + assertEquals(7, count[0]); + return null; + }).execute(null, null, null, null); + } + + @Test + public void testCanSupplyBadMaximumValue() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + + db.beginTransactionAsync((Function) databaseWrapper -> { + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + return null; + }).success((objectTransaction, o) -> { + FlowCursorIterator iterator = SQLite.select().from(SimpleModel.class).cursorList(db).iterator(2, Long.MAX_VALUE); + final int[] count = {0}; + + iterator.forEachRemaining(simpleModel -> { + assertEquals(String.valueOf(count[0] + 2), simpleModel.name); + count[0]++; + }); + assertEquals(8, count[0]); + return null; + }).execute(null, null, null, null); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorListTest.java b/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..447ca17d5e5d094a90e59606fbd1418beef63b53 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/query/list/FlowCursorListTest.java @@ -0,0 +1,79 @@ +package com.dbflow5.query.list; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import java.util.List; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; + +public class FlowCursorListTest extends BaseUnitTest { + + @Test + public void validateCursorPassed() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + FlowCursor cursor = SQLite.select().from(SimpleModel.class).cursor(db); + + FlowCursorList list = new FlowCursorList.Builder(SQLite.select().from(SimpleModel.class), db).cursor(cursor).build(); + + assertEquals(cursor, list.cursor()); + } + + @Test + public void validateModelQueriable() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + ModelQueriable from = SQLite.select().from(SimpleModel.class); + + FlowCursorList list = new FlowCursorList.Builder(from, db).build(); + + assertEquals(from, list.modelQueriable); + + } + + @Test + public void validateGetAll() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + FlowCursorList list = SQLite.select().from(SimpleModel.class).cursorList(db); + List all = list.all(); + assertEquals(list.count(), all.size()); + } + + @Test + public void validateCursorChange() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + FlowCursorList list = SQLite.select().from(SimpleModel.class).cursorList(db); + final int[] count = {0}; + final FlowCursorList[] cursorListFound = new FlowCursorList[1]; + + Function, Void> listener = simpleModels -> { + cursorListFound[0] = simpleModels; + count[0]++; + return null; + }; + + list.addOnCursorRefreshListener(listener); + assertEquals(10, list.count()); + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(10)), db); + list.refresh(); + assertEquals(11, list.count()); + assertEquals(list, cursorListFound[0]); + list.removeOnCursorRefreshListener(listener); + list.refresh(); + assertEquals(1, count[0]); + } + +} diff --git a/entry/src/ohosTest/java/com/dbflow5/runtime/DirectNotifierTest.java b/entry/src/ohosTest/java/com/dbflow5/runtime/DirectNotifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e1762381300cfb53da427491d5bcdb3bbf95e781 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/runtime/DirectNotifierTest.java @@ -0,0 +1,86 @@ +package com.dbflow5.runtime; + +import com.dbflow5.DemoApp; +import com.dbflow5.ImmediateTransactionManager; +import com.dbflow5.TestDatabase; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseConfig; +import com.dbflow5.config.FlowConfig; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.OhosSQLiteOpenHelper; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.SQLite; +import com.dbflow5.structure.ChangeAction; + +import decc.testkit.runner.HarmonyJUnitClassRunner; +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import ohos.app.Context; +import ohos.utils.Pair; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +@RunWith(HarmonyJUnitClassRunner.class) +public class DirectNotifierTest { + private final Context context = AbilityDelegatorRegistry.getAbilityDelegator().getAppContext(); + + @Before + public void setupTest() { + FlowManager.init(context, builder -> { + + DatabaseConfig.Builder builder1 = DatabaseConfig.builder(TestDatabase.class, OhosSQLiteOpenHelper.createHelperCreator(DemoApp.context)); + builder1.transactionManagerCreator(ImmediateTransactionManager::new); + FlowConfig.builder(context) + .database(builder1.build()).build(); + return null; + }); + } + + @Test + public void validateCanNotifyDirect() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + SimpleModel simpleModel = new SimpleModel("Name"); + DirectModelNotifier.OnModelStateChangedListener modelChange = Mockito.mock( + DirectModelNotifier.OnModelStateChangedListener.class, + Mockito.withSettings()); + DirectModelNotifier.get().registerForModelStateChanges(SimpleModel.class, modelChange); + FlowManager.getModelAdapter(SimpleModel.class).insert(simpleModel, db); + Mockito.verify(modelChange).onModelChanged(simpleModel, ChangeAction.INSERT); + + FlowManager.getModelAdapter(SimpleModel.class).update(simpleModel, db); + Mockito.verify(modelChange).onModelChanged(simpleModel, ChangeAction.UPDATE); + + FlowManager.getModelAdapter(SimpleModel.class).save(simpleModel, db); + Mockito.verify(modelChange).onModelChanged(simpleModel, ChangeAction.CHANGE); + + FlowManager.getModelAdapter(SimpleModel.class).delete(simpleModel, db); + Mockito.verify(modelChange).onModelChanged(simpleModel, ChangeAction.DELETE); + } + + @Test + public void validateCanNotifyWrapperClasses(){ + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + OnTableChangedListener modelChange = Mockito.mock( + OnTableChangedListener.class, + Mockito.withSettings()); + DirectModelNotifier.get().registerForTableChanges(SimpleModel.class, modelChange); + + SQLite.insertInto(SimpleModel.class).columnValues(new Pair<>(SimpleModel_Table.name, "name")).executeInsert(db); + Mockito.verify(modelChange).onTableChanged(SimpleModel.class, ChangeAction.INSERT); + + + SQLite.update(SimpleModel.class).set(SimpleModel_Table.name.eq("name2")).executeUpdateDelete(db); + Mockito.verify(modelChange).onTableChanged(SimpleModel.class, ChangeAction.UPDATE); + + SQLite.delete(SimpleModel.class).executeUpdateDelete(db); + Mockito.verify(modelChange).onTableChanged(SimpleModel.class, ChangeAction.DELETE); + } + + @After + public void teardown(){ + FlowManager.destroy(); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/RXTestRule.java b/entry/src/ohosTest/java/com/dbflow5/rx2/RXTestRule.java new file mode 100644 index 0000000000000000000000000000000000000000..4636c2c0907fdf0f45f1763a1d5d8fef865a1f79 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/RXTestRule.java @@ -0,0 +1,27 @@ +package com.dbflow5.rx2; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import io.reactivex.rxjava3.plugins.RxJavaPlugins; +import io.reactivex.rxjava3.schedulers.Schedulers; + +/** + * RXTestRule + * + * @author wangyin + * @since 2021/06/21 + */ +public class RXTestRule implements TestRule { + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() { + RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); + RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); + } + }; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/TransactionObservablesTest.java b/entry/src/ohosTest/java/com/dbflow5/rx2/TransactionObservablesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d0783d3a4a7a7d305ad99edf694e54748b7a6c9a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/TransactionObservablesTest.java @@ -0,0 +1,66 @@ +package com.dbflow5.rx2; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.TestDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.SQLite; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.transaction.ITransaction; +import com.dbflow5.transaction.Transaction; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class TransactionObservablesTest extends BaseUnitTest { + + @Test + public void testObservableRun() { + final boolean[] successCalled = new boolean[1]; + final List[] list = new List[]{new ArrayList<>()}; + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + Transaction.Builder builder = db.beginTransactionAsync(new Function() { + @Override + public TestDatabase apply(DatabaseWrapper databaseWrapper) { + for (int i = 0; i < 10; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), db); + } + return null; + } + }); + TransactionObservable.SingleTransaction transaction = new TransactionObservable.SingleTransaction<>(builder); + transaction.doAfterSuccess(testDatabase -> { + TestDatabase db1 = FlowManager.getDatabase(TestDatabase.class); + + Transaction.Builder> builder1 = db1.beginTransactionAsync((ITransaction>) databaseWrapper -> SQLite.select().from(SimpleModel.class).queryList(db1)); + TransactionObservable.SingleTransaction> transaction1 = new TransactionObservable.SingleTransaction<>(builder1); + transaction1.subscribe(loadedList -> { + list[0] = loadedList; + successCalled[0] = true; + }); + }); + transaction.subscribe(); + assertTrue(successCalled[0]); + assertEquals(10, list[0].size()); + } + + @Test + public void testMaybe() { + AtomicReference simpleModel = new AtomicReference<>(new SimpleModel("")); + + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + Transaction.Builder builder = db.beginTransactionAsync((ITransaction) databaseWrapper -> SQLite.select().from(SimpleModel.class).querySingle(db)); + TransactionObservable.MaybeTransaction maybeTransaction = new TransactionObservable.MaybeTransaction<>(builder); + maybeTransaction.subscribe(simpleModel::set); + assertNull(simpleModel); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.java b/entry/src/ohosTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b6fdda235dde277bc1a6b1e786b1fd1aafa8b1e8 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.java @@ -0,0 +1,103 @@ +package com.dbflow5.rx2.query; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.query.SQLite; +import com.dbflow5.reactivestreams.query.CursorListFlowable; +import com.dbflow5.reactivestreams.query.ModelQueriableExtensions; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.structure.Model; +import com.dbflow5.transaction.ITransaction; + +import io.reactivex.rxjava3.core.Flowable; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; + +public class CursorResultSubscriberTest extends BaseUnitTest { + @Test + public void testCanQueryStreamResults() { + FlowManager.databaseForTable(SimpleModel.class, db -> { + for(int i = 0; i <= 9; i++) { + Model.save(SimpleModel.class, new SimpleModel(String.valueOf(i)), db); + } + + AtomicInteger count = new AtomicInteger(); + ModelQueriableExtensions.queryStreamResults(SQLite.select().from(SimpleModel.class), db) + .subscribe(simpleModel -> { + count.getAndIncrement(); + assert(simpleModel != null); + }); + + assertEquals(10, count.get()); + return null; + }); + } + + @Test + public void testCanObserveOnTableChangesWithModelOps() { + AtomicInteger count = new AtomicInteger(); + + TransactionObservable.asFlowable(SQLite.select().from(SimpleModel.class), (modelModelQueriable, db) -> modelModelQueriable.queryList(db)).subscribe(simpleModel -> { + count.getAndIncrement(); + }); + SimpleModel model = new SimpleModel("test"); + + FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> null).executeTransactionFunc(db -> { + Model.save(SimpleModel.class, model, db); + Model.delete(SimpleModel.class, model, db); + Model.insert(SimpleModel.class, model, db); + return null; + }); + assertEquals(2, count.get()); + } + + @Test + public void testCanObserveOnTableChangesWithTableOps() { + FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> { + SQLite.delete(SimpleModel.class).executeUpdateDelete(dbFlowDatabase); + + AtomicInteger count = new AtomicInteger(); + + AtomicReference> curList = new AtomicReference<>(new ArrayList<>()); + + Flowable> flowable = TransactionObservable.asFlowable(SQLite.select().from(SimpleModel.class), (BiFunction, DatabaseWrapper, List>) ModelQueriable::queryList); + flowable.subscribe(simpleModels -> { + count.getAndIncrement(); + curList.set(simpleModels); + }); + dbFlowDatabase.executeTransaction((ITransaction) databaseWrapper -> { + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test").executeInsert(dbFlowDatabase); + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test1").executeInsert(dbFlowDatabase); + SQLite.insert(SimpleModel.class, SimpleModel_Table.name).values("test2").executeInsert(dbFlowDatabase); + return null; + }); + + assertEquals(3, curList.get().size()); + + dbFlowDatabase.executeTransaction((ITransaction) databaseWrapper -> { + SimpleModel model = SQLite.select().from(SimpleModel.class).where(SimpleModel_Table.name.eq("test")).requireSingle(dbFlowDatabase); + Model.delete(Object.class, model, dbFlowDatabase); + return null; + }); + + dbFlowDatabase.tableObserver().checkForTableUpdates(); + assertEquals(2, curList.get().size()); + assertEquals(3, count.get()); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXFlowableTest.java b/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXFlowableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9c6e14021230ceca19c40b635862c5b5f8301492 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXFlowableTest.java @@ -0,0 +1,102 @@ +package com.dbflow5.rx2.query; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.TestDatabase; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.models.Author_Table; +import com.dbflow5.models.Blog_Table; +import com.dbflow5.models.ForeignKeyModels; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.Method; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.structure.Model; +import com.dbflow5.transaction.ITransaction; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; + +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.disposables.Disposable; + +import static org.junit.Assert.assertEquals; + +public class RXFlowableTest extends BaseUnitTest { + @Test + public void testCanObserveChanges() { + FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> { + for (int i = 0; i < 100; i++) { + FlowManager.getModelAdapter(SimpleModel.class).save(new SimpleModel(String.valueOf(i)), dbFlowDatabase); + } + + AtomicReference> list = new AtomicReference<>(new ArrayList<>()); + + AtomicInteger triggerCount = new AtomicInteger(); + + + Flowable> flowable = TransactionObservable.asFlowable(SQLite.select().from(SimpleModel.class) + .where(Method.cast(SimpleModel_Table.name).asInteger().greaterThan(50)), ModelQueriable::queryList); + + Disposable subscribe = flowable.subscribe(simpleModels -> { + triggerCount.addAndGet(1); + list.set(simpleModels); + }); + assertEquals(50, list.get().size()); + subscribe.dispose(); + + Model.save(Object.class, new SimpleModel("should not trigger"), dbFlowDatabase); + assertEquals(1, triggerCount.get()); + return null; + }); + } + + @Test + public void testObservesJoinTables() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(TestDatabase.class); + Operator joinOn = Blog_Table.name.withTable() + .eq(Author_Table.first_name.withTable() + " " + Author_Table.last_name.withTable()); + assertEquals("Blog.name=Author.first_name+' '+Author.last_name", joinOn.getQuery()); + FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> { + AtomicReference> list = new AtomicReference<>(new ArrayList<>()); + AtomicInteger calls = new AtomicInteger(); + + + Flowable> flowable = TransactionObservable.asFlowable(SQLite.select().from(ForeignKeyModels.Blog.class) + .leftOuterJoin(ForeignKeyModels.Author.class).on(joinOn), (BiFunction, DatabaseWrapper, List>) ModelQueriable::queryList); + + Disposable subscribe = flowable.subscribe(blogs -> { + calls.getAndIncrement(); + list.set(blogs); + + }); + + List authorList = new ArrayList<>(); + for (int i = 1; i < 11; i++) { + authorList.add(new ForeignKeyModels.Author(i, i + "name", i + "last")); + } + db.executeTransaction(new ITransaction() { + @Override + public Object execute(DatabaseWrapper databaseWrapper) { + for (int i = 1; i < 11; i++) { + Model.save(Object.class, new ForeignKeyModels.Blog(i, i + "name" + " " + i + "last", authorList.get(i - 1)), databaseWrapper); + } + return null; + } + }); + + assertEquals(10, list.get().size()); + assertEquals(2, calls.get()); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXQueryTests.java b/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXQueryTests.java new file mode 100644 index 0000000000000000000000000000000000000000..f63bdc3d4dc33eaa4747959274d794b755c97996 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/query/RXQueryTests.java @@ -0,0 +1,64 @@ +package com.dbflow5.rx2.query; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.Property; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.structure.Model; +import com.dbflow5.transaction.ITransaction; +import com.dbflow5.transaction.Transaction; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; + +public class RXQueryTests extends BaseUnitTest { + @Test + public void testCanQuery() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + Model.save(Object.class, new SimpleModel("Name"), db); + final FlowCursor[] cursor = new FlowCursor[1]; + Transaction.Builder builder = db.beginTransactionAsync((ITransaction) databaseWrapper -> SQLite.select().from(SimpleModel.class).cursor(databaseWrapper)); + TransactionObservable.asMaybe(builder).subscribe(o -> cursor[0] = o); + assertEquals(1, cursor[0].getRowCount()); + cursor[0].close(); + } + + @Test + public void testCanCompileStatement() { + final DatabaseStatement[] databaseStatement = new DatabaseStatement[1]; + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + Transaction.Builder builder = db.beginTransactionAsync((ITransaction) databaseWrapper -> SQLite.insert(SimpleModel.class, SimpleModel_Table.name.is("name")).compileStatement(databaseWrapper)); + TransactionObservable.asSingle(builder).subscribe((statement, throwable) -> databaseStatement[0] = statement); + databaseStatement[0].close(); + } + + @Test + public void testCountMethod() { + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + Model.save(Object.class, new SimpleModel("name"), db); + Model.save(Object.class, new SimpleModel("name2"), db); + AtomicLong count = new AtomicLong(); + Transaction.Builder builder = db.beginTransactionAsync((ITransaction) databaseWrapper -> SQLite.selectCountOf(Property.ALL_PROPERTY).from(SimpleModel.class).longValue(db)); + TransactionObservable.asSingle(builder).subscribe(aLong -> count.set(aLong)); + assertEquals(2, count.get()); + } + + @Test + public void testInsertMethod() { + AtomicLong count = new AtomicLong(); + DBFlowDatabase db = FlowManager.getDatabaseForTable(SimpleModel.class); + Transaction.Builder builder = db.beginTransactionAsync((ITransaction) databaseWrapper -> SQLite.insert(SimpleModel.class, SimpleModel_Table.name.eq("name")).executeInsert(db)); + TransactionObservable.asSingle(builder).subscribe(aLong -> count.set(aLong)); + assertEquals(1, count.get()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel.java b/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel.java new file mode 100644 index 0000000000000000000000000000000000000000..e520a167dc2ac45fcb0d559a57240d19ca1b0038 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel.java @@ -0,0 +1,24 @@ +package com.dbflow5.rx2.query; + +import com.dbflow5.TestDatabase; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.reactivestreams.structure.BaseRXModel; + +@Table(database = TestDatabase.class, allFields = true) +public class SimpleRXModel extends BaseRXModel { + @PrimaryKey + public String id = ""; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public SimpleRXModel(String id) { + this.id = id; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..ad77dd469540f5edc0658c17667ad508a0fbc9c2 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/rx2/query/SimpleRXModel_Table.java @@ -0,0 +1,132 @@ +package com.dbflow5.rx2.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import java.lang.Class; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; + +public final class SimpleRXModel_Table extends ModelAdapter { + /** + * Primary Key */ + public static final Property id = new Property(SimpleRXModel.class, "id"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id}; + + public SimpleRXModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return SimpleRXModel.class; + } + + @Override + public final String getName() { + return "SimpleRXModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, SimpleRXModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, SimpleRXModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + if (model.getId() != null) { + statement.bindString(2, model.getId()); + } else { + statement.bindString(2, ""); + } + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, SimpleRXModel model) { + if (model.getId() != null) { + statement.bindString(1, model.getId()); + } else { + statement.bindString(1, ""); + } + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO SimpleRXModel(id) VALUES (?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO SimpleRXModel(id) VALUES (?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE SimpleRXModel SET id=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM SimpleRXModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS SimpleRXModel(id TEXT, PRIMARY KEY(id))"; + } + + @Override + public final SimpleRXModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + SimpleRXModel model = new SimpleRXModel(""); + model.setId(cursor.getStringOrDefault("id", "")); + return model; + } + + @Override + public final OperatorGroup getPrimaryConditionClause(SimpleRXModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/CaseTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/CaseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..70a64959b4de59c1e4bafa302b2b37863362c511 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/CaseTest.java @@ -0,0 +1,23 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.Case; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.PropertyFactory; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CaseTest extends BaseUnitTest { + @Test + public void simpleCaseTest(){ + Case aCase = SQLite._case(PropertyFactory.propertyString(String.class, "country")) + .whenever("USA").then("Domestic")._else("Foreign"); + + assertEquals("CASE country WHEN 'USA' THEN 'Domestic' ELSE 'Foreign' END Country", + aCase.end("Country").getQuery().trim()); + assertTrue(aCase.isEfficientCase); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/DeleteTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/DeleteTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0697ae1489a0d3a0bd388ff697e38abf08fd2d5d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/DeleteTest.java @@ -0,0 +1,47 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.FlowManager; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.Where; +import com.dbflow5.structure.Model; + +import org.junit.Test; + +import static com.dbflow5.query.SQLite.delete; +import static com.dbflow5.query.SQLite.select; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class DeleteTest extends BaseUnitTest { + @Test + public void validateQuery(){ + assertEquals("DELETE ", delete().getQuery()); + } + + @Test + public void validateDeletion(){ + FlowManager.databaseForTable(SimpleModel.class, dbFlowDatabase -> { + Model.save(SimpleModel.class, new SimpleModel("name"), dbFlowDatabase); + SQLite.delete(SimpleModel.class).execute(dbFlowDatabase); + assertFalse((select().from(SimpleModel.class)).hasData(dbFlowDatabase)); + return null; + }); + } + + @Test + public void validateDeletionWithQuery(){ + FlowManager.databaseForTable(SimpleModel.class,dbFlowDatabase -> { + Model.save(SimpleModel.class, new SimpleModel("name"), dbFlowDatabase); + Model.save(SimpleModel.class, new SimpleModel("another name"), dbFlowDatabase); + Where where=delete(SimpleModel.class).where(SimpleModel_Table.name.is("name")); + assertEquals("DELETE FROM SimpleModel WHERE `name`='name'", where.getQuery().trim()); + where.execute(dbFlowDatabase); + assertEquals(1, (select().from(SimpleModel.class)).queryList(dbFlowDatabase).size()); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3b97cda0c4c25b1394e8715cb0f33457ef0b9b2 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.java @@ -0,0 +1,22 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.ExistenceOperator; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExistenceOperatorTest extends BaseUnitTest { + @Test + public void validateQuery(){ + assertEquals("EXISTS (SELECT * FROM SimpleModel WHERE name='name')", + new ExistenceOperator( + (SQLite.select().from(SimpleModel.class).where(SimpleModel_Table.name.eq("name")))) + .getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/FromTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/FromTest.java new file mode 100644 index 0000000000000000000000000000000000000000..951b1a3cf6ca0df78bdf29cf622582c45544f78c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/FromTest.java @@ -0,0 +1,49 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +//import com.dbflow5.models.SimpleModel_Table.name; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +//import com.dbflow5.models.TwoColumnModel_Table; +//import com.dbflow5.models.TwoColumnModel_Table.id; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.From; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class FromTest extends BaseUnitTest { + @Test + public void validateSimpleFrom(){ + assertEquals("SELECT * FROM SimpleModel", (SQLite.select().from(SimpleTestModels.SimpleModel.class).getQuery().trim())); + } + + @Test + public void validateProjectionFrom(){ + assertEquals("SELECT name FROM SimpleModel", (SQLite.select(SimpleModel_Table.name).from(SimpleTestModels.SimpleModel.class).getQuery()).trim()); + } + + @Test + public void validateMultipleProjection(){ + assertEquals("SELECT name,name,id FROM SimpleModel", + (SQLite.select(SimpleModel_Table.name, TwoColumnModel_Table.name, TwoColumnModel_Table.id).from(SimpleTestModels.SimpleModel.class)).getQuery().trim()); + } + + @Test + public void validateAlias() { + assertEquals("SELECT * FROM SimpleModel AS Simple", (SQLite.select().from(SimpleTestModels.SimpleModel.class).as("Simple")).getQuery().trim()); + } + + @Test + public void validateJoins() { + From from = SQLite.select().from(SimpleTestModels.SimpleModel.class).innerJoin(SimpleTestModels.TwoColumnModel.class).on(SimpleModel_Table.name.eq(TwoColumnModel_Table.name.withTable())); + + assertEquals("SELECT * FROM SimpleModel INNER JOIN TwoColumnModel ON name=TwoColumnModel.name", + from.getQuery().trim()); + assertFalse(from.associatedTables().isEmpty()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4a1e8f03c9f971cb82bd7e95d9f4157511404d27 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexTest.java @@ -0,0 +1,32 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.Index; +import com.dbflow5.query.NameAlias; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IndexTest extends BaseUnitTest { + @Test + public void validateBasicIndex() { + assertEquals("CREATE INDEX IF NOT EXISTS index ON SimpleModel(name)", + Index.indexOn(SimpleTestModels.SimpleModel.class, "index", SimpleModel_Table.name).getQuery()); + } + + @Test + public void validateUniqueIndex() { + assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS index ON SimpleModel(name, test)", + Index.indexOn(SimpleTestModels.SimpleModel.class, "index").unique(true).and(SimpleModel_Table.name) + .and(NameAlias.of("test")).getQuery()); + } + + @Test + public void validateBasicIndexNameAlias() { + assertEquals("CREATE INDEX IF NOT EXISTS index ON SimpleModel(name, test)", + Index.indexOn(SimpleTestModels.SimpleModel.class, "index", NameAlias.of("name"), NameAlias.of("test")).getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexedByTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexedByTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b3cb4a0a5493980e6bcfe7d89acf74bbe1127a1c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/IndexedByTest.java @@ -0,0 +1,21 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.IndexedBy; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IndexProperty; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IndexedByTest extends BaseUnitTest { + @Test + public void validateQuery() { + IndexedBy indexed = (SQLite.select().from(SimpleTestModels.SimpleModel.class)) + .indexedBy(new IndexProperty<>("Index", false, SimpleTestModels.SimpleModel.class, SimpleModel_Table.name)); + assertEquals("SELECT * FROM SimpleModel INDEXED BY Index", indexed.getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/InsertTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/InsertTest.java new file mode 100644 index 0000000000000000000000000000000000000000..77ecbe3ad95d3d2cde4bae83f36bc27e405164a5 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/InsertTest.java @@ -0,0 +1,87 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.Operator; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; + +import com.dbflow5.query.property.IProperty; +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; + +public class InsertTest extends BaseUnitTest { + @Test + public void validateInsert() { + assertEquals("INSERT INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).values("something").getQuery().trim()); + } + + @Test + public void validateInsertOr() { + assertEquals("INSERT OR REPLACE INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orReplace().values("something").getQuery().trim()); + assertEquals("INSERT OR FAIL INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orFail().values("something").getQuery().trim()); + assertEquals("INSERT OR IGNORE INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orIgnore().values("something").getQuery().trim()); + assertEquals("INSERT OR REPLACE INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orReplace().values("something").getQuery().trim()); + assertEquals("INSERT OR ROLLBACK INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orRollback().values("something").getQuery().trim()); + assertEquals("INSERT OR ABORT INTO SimpleModel VALUES('something')", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).orAbort().values("something").getQuery().trim()); + } + + @Test + public void validateQuestionIntention() { + assertEquals("INSERT INTO SimpleModel VALUES(?)", + SQLite.insertInto(SimpleTestModels.SimpleModel.class).values("?").getQuery().trim()); + } + + @Test + public void validateInsertProjection() { + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, id)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columns(TwoColumnModel_Table.name, TwoColumnModel_Table.id).values("name", "id").getQuery().trim()); + } + + @Test + public void validateSelect() { + assertEquals("INSERT INTO TwoColumnModel SELECT * FROM SimpleModel", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).select(SQLite.select().from(SimpleTestModels.SimpleModel.class)).getQuery().trim()); + } + + @Test + public void validateColumns() { + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, id)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).asColumns().values("name", "id").getQuery().trim()); + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, id)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columns("name", "id").values("name", "id").getQuery().trim()); + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, id)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columns(new ArrayList>() { + { + add(TwoColumnModel_Table.name); + add(TwoColumnModel_Table.id); + } + }).values("name", "id").getQuery().trim()); + + } + + @Test + public void validateColumnValues() { + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, 0)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columnValues(SimpleModel_Table.name.eq("name"), TwoColumnModel_Table.id.eq(0)).getQuery().trim()); + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, 0)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columnValues(Operator.op(NameAlias.builder("name").build()).eq("name"), + TwoColumnModel_Table.id.eq(0)).getQuery().trim()); + assertEquals("INSERT INTO TwoColumnModel(name, id) VALUES(name, 0)", + SQLite.insertInto(SimpleTestModels.TwoColumnModel.class).columnValues(OperatorGroup.clause().andAll(TwoColumnModel_Table.name.eq("name"), TwoColumnModel_Table.id.eq(0))).getQuery().trim()); + + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/JoinTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/JoinTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2b1b9c6b4f77d6820dd965de0a8ceeea91054f3d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/JoinTest.java @@ -0,0 +1,65 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.From; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class JoinTest extends BaseUnitTest { + @Test + public void validateAliasJoin() { + assertEquals("SELECT * FROM SimpleModel INNER JOIN TwoColumnModel AS Name ON TwoColumnModel.name=name", + ((SQLite.select().from(SimpleTestModels.SimpleModel.class).innerJoin(SimpleTestModels.TwoColumnModel.class).as("Name").on(TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)).getQuery().trim()))); + } + + @Test + public void testInnerJoin() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).innerJoin(SimpleTestModels.TwoColumnModel.class).on(TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)); + assertEquals("SELECT * FROM SimpleModel INNER JOIN TwoColumnModel ON TwoColumnModel.name=name", + join.getQuery().trim()); + } + + @Test + public void testLeftOuterJoin() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).leftOuterJoin(SimpleTestModels.TwoColumnModel.class).on(TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)); + assertEquals("SELECT * FROM SimpleModel LEFT OUTER JOIN TwoColumnModel ON TwoColumnModel.name=name", + join.getQuery().trim()); + } + + @Test + public void testCrossJoin() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).crossJoin(SimpleTestModels.TwoColumnModel.class).on(TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)); + assertEquals("SELECT * FROM SimpleModel CROSS JOIN TwoColumnModel ON TwoColumnModel.name=name", + join.getQuery().trim()); + } + + @Test + public void testMultiJoin() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).innerJoin(SimpleTestModels.TwoColumnModel.class).on(TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)).crossJoin(SimpleTestModels.TwoColumnModel.class) + .on(TwoColumnModel_Table.id.withTable().eq(SimpleModel_Table.name)); + assertEquals("SELECT * FROM SimpleModel INNER JOIN TwoColumnModel ON TwoColumnModel.name=name" + + " CROSS JOIN TwoColumnModel ON TwoColumnModel.id=name", + join.getQuery().trim()); + } + + @Test + public void testInnerJoinOnUsing() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).innerJoin(SimpleTestModels.TwoColumnModel.class).using(SimpleModel_Table.name.withTable()); + assertEquals("SELECT * FROM SimpleModel INNER JOIN TwoColumnModel USING (SimpleModel.name)", + join.getQuery().trim()); + } + + @Test + public void testNaturalJoin() { + From join = SQLite.select().from(SimpleTestModels.SimpleModel.class).naturalJoin(SimpleTestModels.TwoColumnModel.class).end(); + assertEquals("SELECT * FROM SimpleModel NATURAL JOIN TwoColumnModel", + join.getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/MethodTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/MethodTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6ca7dff25b27c2bcc02b93e3a7ef6c245b04be51 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/MethodTest.java @@ -0,0 +1,70 @@ +package com.dbflow5.sql.language; + +import static com.dbflow5.models.TwoColumnModel_Table.id; +import static com.dbflow5.models.TwoColumnModel_Table.name; +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.Method; +import com.dbflow5.sql.SQLiteType; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MethodTest extends BaseUnitTest { + @Test + public void testMainMethods() { + assertEquals("AVG(name, id)", Method.avg(name, id).getQuery()); + assertEquals("COUNT(name, id)", Method.count(name, id).getQuery()); + assertEquals("GROUP_CONCAT(name, id)", Method.groupConcat(name, id).getQuery()); + assertEquals("MAX(name, id)", Method.max(name, id).getQuery()); + assertEquals("MIN(name, id)", Method.min(name, id).getQuery()); + assertEquals("SUM(name, id)", Method.sum(name, id).getQuery()); + assertEquals("TOTAL(name, id)", Method.total(name, id).getQuery()); + assertEquals("CAST(name AS INTEGER)", Method.cast(name).as(SQLiteType.INTEGER).getQuery()); + assertEquals("REPLACE(name, Andrew, Grosner)", Method.replace(name, "Andrew", "Grosner").getQuery()); + } + + @Test + public void test_strftime() { + assertEquals("strftime(%s, now)", Method.strftime("%s", "now").getQuery()); + } + + @Test + public void test_dateMethod() { + assertEquals("date(now, start of month, +1 month)", + Method.date("now", "start of month", "+1 month").getQuery()); + } + + @Test + public void test_datetimeMethod() { + assertEquals("datetime(1092941466, unix epoch)", + Method.datetime(1092941466, "unix epoch").getQuery()); + } + + @Test + public void testIfNull() { + assertEquals("IFNULL(name, id)", Method.ifNull(name, id).getQuery()); + } + + @Test + public void testNulllIf() { + assertEquals("NULLIF(name, id)", Method.nullIf(name, id).getQuery()); + } + + @Test + public void random_generates_correct_query() { + assertEquals("RANDOM()", Method.random().getQuery()); + } + + @Test + public void testOpMethods() { + assertEquals("AVG(name + id)", Method.avg(name.plus(id)).getQuery()); + assertEquals("AVG(name + id)", (Method.avg(name).plus(id)).getQuery()); + assertEquals("AVG(name - id)", Method.avg(name.minus(id)).getQuery()); + assertEquals("AVG(name - id)", (Method.avg(name).minus(id)).getQuery()); + assertEquals("AVG(name / id)", Method.avg(name.div(id)).getQuery()); + assertEquals("AVG(name * id)", (Method.avg(name).times(id)).getQuery()); + assertEquals("AVG(name % id)", Method.avg(name.rem(id)).getQuery()); + assertEquals("AVG(name % id)", (Method.avg(name).rem(id)).getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/NameAliasTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/NameAliasTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1200c919c0f099619b8a8797860b2f4984af7fa6 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/NameAliasTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.NameAlias; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class NameAliasTest extends BaseUnitTest { + @Test + public void testSimpleCase() { + assertEquals("name", NameAlias.of("name").getQuery()); + } + + @Test + public void testAlias() { + assertEquals("name AS alias", NameAlias.as("name", "alias").fullQuery()); + } + + @Test + public void validateBuilder() { + NameAlias nameAlias = NameAlias.builder("name") + .keyword("DISTINCT") + .as("Alias") + .withTable("MyTable") + .shouldAddIdentifierToAliasName(false) + .shouldAddIdentifierToName(false) + .shouldStripAliasName(false) + .shouldStripIdentifier(false).build(); + assertEquals("DISTINCT", nameAlias.keyword); + assertEquals("Alias", nameAlias.aliasName()); + assertEquals("Alias", nameAlias.aliasNameRaw()); + assertEquals("MyTable", nameAlias.tableName); + assertFalse(nameAlias.shouldStripAliasName); + assertFalse(nameAlias.shouldStripIdentifier); + assertEquals("Alias", nameAlias.nameAsKey()); + assertEquals("DISTINCT MyTable.name AS Alias", nameAlias.fullQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorGroupTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorGroupTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5164e7965a2dbc3b2ef42cf23ec36042fb2e9fa3 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorGroupTest.java @@ -0,0 +1,50 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLOperator; + +import static com.dbflow5.models.TwoColumnModel_Table.id; +import static com.dbflow5.models.TwoColumnModel_Table.name; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + +public class OperatorGroupTest extends BaseUnitTest { + @Test + public void validateCommaSeparated() { + assertEquals("(name='name', id=0)", OperatorGroup.clause().setAllCommaSeparated(true).andAll(name.eq("name"), id.eq(0)).getQuery().trim()); + } + + @Test + public void validateParanthesis() { + assertEquals("name='name'", OperatorGroup.nonGroupingClause(name.eq("name")).setUseParenthesis(false).getQuery().trim()); + } + + @Test + public void validateOr() { + assertEquals("(name='name' OR id=0)", name.eq("name").or(id.eq(0)).getQuery().trim()); + } + + @Test + public void validateOrAll() { + assertEquals("(name='name' OR id=0 OR name='test')", name.eq("name").orAll(Arrays.asList(id.eq(0), name.eq("test"))).getQuery().trim()); + } + + @Test + public void validateAnd() { + assertEquals("(name='name' AND id=0)", name.eq("name").and(id.eq(0)).getQuery().trim()); + } + + @Test + public void validateAndAll() { + assertEquals("(name='name' AND id=0 AND name='test')", name.eq("name").andAll(new ArrayList() {{ + add(id.eq(0)); + add(name.eq("test")); + }}).getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..10e5c0c6a3db40564e478826be3bfe5c83bda2be --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/OperatorTest.java @@ -0,0 +1,72 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.annotation.Collate; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.Operator; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + +public class OperatorTest extends BaseUnitTest { + @Test + public void testEquals() { + assertEquals("name='name'", Operator.op("name").eq("name").getQuery()); + assertEquals("name='name'", Operator.op("name").is("name").getQuery()); + } + + @Test + public void testNotEquals() { + + assertEquals("name!='name'", Operator.op("name").notEq("name").getQuery()); + assertEquals("name!='name'", Operator.op("name").isNot("name").getQuery()); + } + + @Test + public void testLike() { + assertEquals("name LIKE 'name'", Operator.op("name").like("name").getQuery()); + assertEquals("name NOT LIKE 'name'", Operator.op("name").notLike("name").getQuery()); + assertEquals("name GLOB 'name'", Operator.op("name").glob("name").getQuery()); + } + + @Test + public void testMath() { + assertEquals("name>'name'", Operator.op("name").greaterThan("name").getQuery()); + assertEquals("name>='name'", Operator.op("name").greaterThanOrEq("name").getQuery()); + assertEquals("name<'name'", Operator.op("name").lessThan("name").getQuery()); + assertEquals("name<='name'", Operator.op("name").lessThanOrEq("name").getQuery()); + assertEquals("name+'name'", Operator.op("name").plus("name").getQuery()); + assertEquals("name/'name'", Operator.op("name").div("name").getQuery()); + assertEquals("name*'name'", Operator.op("name").times("name").getQuery()); + assertEquals("name%'name'", Operator.op("name").rem("name").getQuery()); + } + + @Test + public void testCollate() { + assertEquals("name COLLATE NOCASE", Operator.op("name").collate(Collate.NOCASE).getQuery()); + assertEquals("name COLLATE NOCASE", Operator.op("name").collate("NOCASE").getQuery()); + } + + @Test + public void testBetween() { + assertEquals("id BETWEEN 6 AND 7", TwoColumnModel_Table.id.between(6).and(7).getQuery().trim()); + } + + @Test + public void testIn() { + assertEquals("id IN (5,6,7,8,9)", TwoColumnModel_Table.id.in(Arrays.asList(5, 6, 7, 8)).and(9).getQuery()); + assertEquals("id NOT IN (SELECT * FROM SimpleModel)", TwoColumnModel_Table.id.notIn(SQLite.select().from(SimpleTestModels.SimpleModel.class))); + } + + @Test + public void matchOperator() { + assertEquals("name MATCH 'age'", Operator.op("name").match("age").getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/OrderByTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/OrderByTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2cf80d11bee126af1bbdf1d4e6f308ce07976a9c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/OrderByTest.java @@ -0,0 +1,33 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.SimpleModel_Table; +import com.dbflow5.annotation.Collate; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.OrderBy; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class OrderByTest extends BaseUnitTest { + @Test + public void validateBasicOrderBy() { + assertEquals("name ASC", OrderBy.fromProperty(SimpleModel_Table.name, true).ascending().getQuery().trim()); + } + + @Test + public void validateDescendingOrderBy() { + assertEquals("name DESC", OrderBy.fromNameAlias(NameAlias.of("name"), true).descending().getQuery().trim()); + } + + @Test + public void validateCollate() { + assertEquals("name COLLATE RTRIM ASC", OrderBy.fromProperty(SimpleModel_Table.name, true).ascending().collate(Collate.RTRIM).getQuery().trim()); + } + + @Test + public void validateCustomOrdrBy() { + assertEquals("name ASC This is custom", OrderBy.fromString("name ASC This is custom").getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/SelectTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/SelectTest.java new file mode 100644 index 0000000000000000000000000000000000000000..48f61990bfc57c44f9ade0dab0aafc0b531f37c0 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/SelectTest.java @@ -0,0 +1,25 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.SimpleTestModels.TwoColumnModel; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.OrderBy; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SelectTest extends BaseUnitTest { + @Test + public void validateSelect() { + assertEquals("SELECT name, id FROM TwoColumnModel", SQLite.select(TwoColumnModel_Table.name, TwoColumnModel_Table.id).from(TwoColumnModel.class).getQuery().trim()); + } + + @Test + public void validateSelectDistinct() { + assertEquals("SELECT DISTINCT name FROM SimpleModel", SQLite.select(TwoColumnModel_Table.name).distinct().from(SimpleTestModels.SimpleModel.class).getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/SetTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/SetTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0a29bbccdc2f3092d1114b8de2cf8c05953df717 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/SetTest.java @@ -0,0 +1,23 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.SQLite; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SetTest extends BaseUnitTest { + @Test + public void validateSetWithConditions() { + assertEquals("UPDATE SimpleModel SET name='name'", SQLite.update(SimpleTestModels.SimpleModel.class).set(SimpleModel_Table.name.is("name")).getQuery().trim()); + } + + @Test + public void validateMultipleConditions() { + assertEquals("UPDATE SimpleModel SET name='name', id=0", SQLite.update(SimpleTestModels.SimpleModel.class).set(SimpleModel_Table.name.eq("name")).and(TwoColumnModel_Table.id.eq(0)).getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/TriggerTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/TriggerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..360345018eaa9fcae372031f27e66060e7016b1a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/TriggerTest.java @@ -0,0 +1,71 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.SimpleTestModels.TwoColumnModel; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.*; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.PropertyFactory; +import com.dbflow5.sql.SQLiteType; + +import com.dbflow5.structure.Model; +import ohos.utils.Pair; +import org.junit.Test; + +import java.util.function.Function; + +import static com.dbflow5.query.SQLite.createTempTrigger; +import static com.dbflow5.query.SQLite.createTrigger; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TriggerTest extends BaseUnitTest { + @Test + public void validateBasicTrigger() { + assertEquals("CREATE TRIGGER IF NOT EXISTS MyTrigger AFTER INSERT ON SimpleModel " + + "\nBEGIN" + + "\nINSERT INTO TwoColumnModel(name) VALUES(new.name);" + + "\nEND", createTrigger("MyTrigger").after().insertOn(SimpleTestModels.SimpleModel.class).begin( + SQLite.insert(SimpleTestModels.TwoColumnModel.class).columnValues(new Pair<>(SimpleModel_Table.name, NameAlias.ofTable("new", "name"))) + ).getQuery().trim() + ); + } + + @Test + public void validateUpdateTriggerMultiline() { + assertEquals("CREATE TEMP TRIGGER IF NOT EXISTS MyTrigger BEFORE UPDATE ON SimpleModel " + + "\nBEGIN" + + "\nINSERT INTO TwoColumnModel(name) VALUES(new.name);" + + "\nINSERT INTO TwoColumnModel(id) VALUES(CAST(new.name AS INTEGER));" + + "\nEND", createTempTrigger("MyTrigger").before().updateOn(SimpleModel.class).begin( + SQLite.insert(TwoColumnModel.class, SimpleModel_Table.name).values(NameAlias.ofTable("new", "name")) + ).and(SQLite.insert(TwoColumnModel.class, TwoColumnModel_Table.id).values( + Method.cast( + PropertyFactory.from( + NameAlias.ofTable("new", "name") + ) + ).as(SQLiteType.INTEGER) + )).getQuery() + ); + } + + @Test + public void validateTriggerWorks() { + FlowManager.databaseForTable(SimpleTestModels.SimpleModel.class, db -> { + CompletedTrigger trigger = createTrigger("MyTrigger").after().insertOn(SimpleModel.class).begin( + SQLite.insert(TwoColumnModel.class).columnValues(new Pair<>(SimpleModel_Table.name, NameAlias.ofTable("new", "name"))) + ); + + trigger.enable(db); + Model.insert(SimpleModel.class, new SimpleModel("Test"), db); + Where result = SQLite.select().from(TwoColumnModel.class).where(SimpleModel_Table.name.eq("Test")); + assertNotNull(result); + return null; + }); + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..abcfdd5d9f866b3815b26a18c1b627ac6bc71c7c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.java @@ -0,0 +1,19 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.UnSafeStringOperator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class UnsafeStringOperatorTest extends BaseUnitTest { + @Test + public void testCanIncludeInQuery() { + UnSafeStringOperator op= new UnSafeStringOperator("name = ?, id = ?, test = ?","'name'", "0", "'test'"); + assertEquals("name = 'name', id = 0, test = 'test'", op.getQuery().trim()); + assertEquals("SELECT * FROM SimpleModel WHERE name = 'name', id = 0, test = 'test'", SQLite.select().from(SimpleTestModels.SimpleModel.class).where(op).getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/UpdateTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/UpdateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2bf68851c9f83576a4c6e0d83bb82771b238796a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/UpdateTest.java @@ -0,0 +1,52 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.models.NumberModel_Table; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.SimpleTestModels.SimpleModel; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class UpdateTest extends BaseUnitTest { + @Test + public void validateUpdateRollback() { + assertEquals("UPDATE OR ROLLBACK SimpleModel", SQLite.update(SimpleModel.class).orRollback().getQuery().trim()); + } + + @Test + public void validateUpdateFail() { + assertEquals("UPDATE OR FAIL SimpleModel", SQLite.update(SimpleModel.class).orFail().getQuery().trim()); + } + + @Test + public void validateUpdateIgnore() { + assertEquals("UPDATE OR IGNORE SimpleModel", SQLite.update(SimpleModel.class).orIgnore().getQuery().trim()); + } + + @Test + public void validateUpdateReplace() { + assertEquals("UPDATE OR REPLACE SimpleModel", SQLite.update(SimpleModel.class).orReplace().getQuery().trim()); + } + + @Test + public void validateUpdateAbort() { + assertEquals("UPDATE OR ABORT SimpleModel", SQLite.update(SimpleModel.class).orAbort().getQuery().trim()); + } + + @Test + public void validateSetQuery() { + assertEquals("UPDATE SimpleModel SET name='name'", SQLite.update(SimpleModel.class).set(SimpleModel_Table.name.eq("name")).getQuery().trim()); + } + + @Test + public void validateWildcardQuery() { + assertEquals("UPDATE OR FAIL NumberModel SET id=? WHERE id=?", SQLite.update(SimpleTestModels.NumberModel.class).or(ConflictAction.FAIL) + .set(NumberModel_Table.id.eq(Property.WILDCARD)).where(NumberModel_Table.id.eq(Property.WILDCARD)).getQuery().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/WhereTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/WhereTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d33a9fdedb522ab606bfb49419dc1e6632b553fa --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/WhereTest.java @@ -0,0 +1,174 @@ +package com.dbflow5.sql.language; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.Method; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.Operator; +import com.dbflow5.query.OrderBy; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.Where; +import com.dbflow5.query.property.PropertyFactory; + +import static com.dbflow5.models.SimpleModel_Table.name; +import static com.dbflow5.models.TwoColumnModel_Table.id; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.fail; + +import com.dbflow5.models.SimpleTestModels.SimpleModel; + +public class WhereTest extends BaseUnitTest { + @Test + public void validateBasicWhere() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")); + assertEquals("SELECT * FROM SimpleModel WHERE name='name'", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateComplexQueryWhere() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).or(id.eq(1)).and(id.is(0).or(name.eq("hi"))); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' OR id=1 AND (id=0 OR name='hi')", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateGroupBy() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).groupBy(name); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' GROUP BY name", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateGroupByNameAlias() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).groupBy(NameAlias.of("name"), NameAlias.of("id")); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' GROUP BY name,id", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateGroupByNameProps() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).groupBy(name, id); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' GROUP BY name,id", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateHaving() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).having(name.like("That")); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' HAVING name LIKE 'That'", query.getQuery().trim()); + assertCanCopyQuery(query); + assertEquals("SELECT * FROM SimpleModel GROUP BY exampleValue HAVING MIN(ROWID)>5", SQLite.select().from(SimpleModel.class).groupBy(NameAlias.rawBuilder("exampleValue").build()) + .having(Method.min(PropertyFactory.from(NameAlias.rawBuilder("ROWID").build())).greaterThan(5)).getQuery().trim()); + } + + @Test + public void validateLimit() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).limit(10); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' LIMIT 10", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateOffset() { + Where query = SQLite.select().from(SimpleModel.class).where(name.is("name")).offset(10); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' OFFSET 10", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateWhereExists() { +// val query = (select from SimpleModel::class +// whereExists (select(name) from SimpleModel::class where name.like("Andrew"))) +// ("SELECT * FROM `SimpleModel` " + +// "WHERE EXISTS (SELECT `name` FROM `SimpleModel` WHERE `name` LIKE 'Andrew')").assertEquals(query) + + Where query = SQLite.select().from(SimpleModel.class).whereExists( + (SQLite.select(name).from(SimpleModel.class).where(name.like("Andrew"))) + ); + assertEquals("SELECT * FROM SimpleModel " + + "WHERE EXISTS (SELECT name FROM SimpleModel WHERE name LIKE 'Andrew')", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateOrderByWhere() { + Where query = SQLite.select().from(SimpleModel.class).where(name.eq("name")).orderBy(name,true); + assertEquals("SELECT * FROM SimpleModel WHERE name='name' ORDER BY name ASC", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateOrderByWhereAlias() { + Where query = SQLite.select().from(SimpleModel.class).where(name.eq("name")).orderBy(NameAlias.of("name"),true); + assertEquals("SELECT * FROM SimpleModel " + + "WHERE name='name' ORDER BY name ASC", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateOrderBy() { + Where query = SQLite.select().from(SimpleModel.class).where(name.eq("name")) + .orderBy(OrderBy.fromNameAlias(NameAlias.of("name"),true).ascending()); + assertEquals("SELECT * FROM SimpleModel " + + "WHERE name='name' ORDER BY name ASC", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + public void assertCanCopyQuery(Where query) { + Where actual = query.cloneSelf(); + assertEquals(query.getQuery(), actual.getQuery()); + assertNotSame(actual, query); + } + + @Test + public void validateOrderByAll() { + Where query = SQLite.select().from(SimpleTestModels.TwoColumnModel.class).where(name.eq("name")) + .orderByAll(new ArrayList(){{ + add(OrderBy.fromNameAlias(NameAlias.of("name"), true).ascending()); + add(OrderBy.fromNameAlias(NameAlias.of("id"), true).descending()); + }}); + assertEquals("SELECT * FROM TwoColumnModel " + + "WHERE name='name' ORDER BY name ASC,id DESC", query.getQuery().trim()); + assertCanCopyQuery(query); + } + + @Test + public void validateNonSelectThrowError() { + FlowManager.databaseForTable(SimpleModel.class, new Function() { + @Override + public Void apply(DBFlowDatabase db) { + try { + SQLite.update(SimpleModel.class).set(name.is("name")).queryList(db); + fail("Non select passed"); + } catch (IllegalArgumentException i) { + // expected + } + + try { + SQLite.update(SimpleModel.class).set(name.is("name")).queryList(db); + fail("Non select passed"); + } catch (IllegalArgumentException i) { + // expected + } + return null; + } + }); + } + + @Test + public void validate_match_operator() { + Where query = SQLite.select().from(SimpleModel.class).where(Operator.op("name").match("%s")); + assertEquals("SELECT * FROM SimpleModel WHERE name MATCH '%s'", query.getQuery().trim()); + assertCanCopyQuery(query); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/BytePropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/BytePropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2b9c9bf48b66ab0f9b4e7eb014510aedbe82b567 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/BytePropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class BytePropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5", prop.is((byte) 5).getQuery().trim()); + assertEquals("Prop=5", prop.eq((byte) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.notEq((byte) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.isNot((byte) 5).getQuery().trim()); + assertEquals("Prop>5", prop.greaterThan((byte) 5).getQuery().trim()); + assertEquals("Prop>=5", prop.greaterThanOrEq((byte) 5).getQuery().trim()); + assertEquals("Prop<5", prop.lessThan((byte) 5).getQuery().trim()); + assertEquals("Prop<=5", prop.lessThanOrEq((byte) 5).getQuery().trim()); + assertEquals("Prop BETWEEN 5 AND 6", prop.between((byte) 5).and((byte) 6).getQuery().trim()); + assertEquals("Prop IN (5,6,7,8)", prop.in((byte) 5, (byte) 6, (byte) 7, (byte) 8).getQuery().trim()); + assertEquals("Prop NOT IN (5,6,7,8)", prop.notIn((byte) 5, (byte) 6, (byte) 7, (byte) 8).getQuery().trim()); + assertEquals("Prop=Prop + 5", prop.concatenate((byte) 5).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/CharPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/CharPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9e76c174241ffcba9f73c02c4c27108909f650a1 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/CharPropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CharPropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop='5'", prop.is((char) '5').getQuery().trim()); + assertEquals("Prop='5'", prop.eq((char) '5').getQuery().trim()); + assertEquals("Prop!='5'", prop.notEq((char) '5').getQuery().trim()); + assertEquals("Prop!='5'", prop.isNot((char) '5').getQuery().trim()); + assertEquals("Prop>'5'", prop.greaterThan((char) '5').getQuery().trim()); + assertEquals("Prop>='5'", prop.greaterThanOrEq((char) '5').getQuery().trim()); + assertEquals("Prop<'5'", prop.lessThan((char) '5').getQuery().trim()); + assertEquals("Prop<='5'", prop.lessThanOrEq((char) '5').getQuery().trim()); + assertEquals("Prop BETWEEN '5' AND '6'", prop.between((char) '5').and((char) '6').getQuery().trim()); + assertEquals("Prop IN ('5','6','7','8')", prop.in((char) '5', (char) '6', (char) '7', (char) '8').getQuery().trim()); + assertEquals("Prop NOT IN ('5','6','7','8')", prop.notIn((char) '5', (char) '6', (char) '7', (char) '8').getQuery().trim()); + assertEquals("Prop=Prop + '5'", prop.concatenate((char) '5').getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9e712d4816a65a7840a0d0aa52777224eabdbe0b --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class DoublePropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5.0", prop.is(5.0).getQuery().trim()); + assertEquals("Prop=5.0", prop.eq(5.0).getQuery().trim()); + assertEquals("Prop!=5.0", prop.notEq(5.0).getQuery().trim()); + assertEquals("Prop!=5.0", prop.isNot(5.0).getQuery().trim()); + assertEquals("Prop>5.0", prop.greaterThan(5.0).getQuery().trim()); + assertEquals("Prop>=5.0", prop.greaterThanOrEq(5.0).getQuery().trim()); + assertEquals("Prop<5.0", prop.lessThan(5.0).getQuery().trim()); + assertEquals("Prop<=5.0", prop.lessThanOrEq(5.0).getQuery().trim()); + assertEquals("Prop BETWEEN 5.0 AND 6.0", prop.between(5.0).and(6.0).getQuery().trim()); + assertEquals("Prop IN (5.0,6.0,7.0,8.0)", prop.in((double) 5.0, 6.0, 7.0, 8.0).getQuery().trim()); + assertEquals("Prop NOT IN (5.0,6.0,7.0,8.0)", prop.notIn(5.0, 6.0, 7.0, 8.0).getQuery().trim()); + assertEquals("Prop=`Prop` + 5.0", prop.concatenate(5.0).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ac0b979149f41c4198e550784db3f2d8d4c03aae --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class FloatPropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5.0", prop.is((Float) 5f).getQuery().trim()); + assertEquals("Prop=5.0", prop.eq((Float) 5f).getQuery().trim()); + assertEquals("Prop!=5.0", prop.notEq((Float) 5f).getQuery().trim()); + assertEquals("Prop!=5.0", prop.isNot((Float) 5f).getQuery().trim()); + assertEquals("Prop>5.0", prop.greaterThan((Float) 5f).getQuery().trim()); + assertEquals("Prop>=5.0", prop.greaterThanOrEq((Float) 5f).getQuery().trim()); + assertEquals("Prop<5.0", prop.lessThan((Float) 5f).getQuery().trim()); + assertEquals("Prop<=5.0", prop.lessThanOrEq((Float) 5f).getQuery().trim()); + assertEquals("Prop BETWEEN 5.0 AND 6.0", prop.between((Float) 5f).and((Float) 6f).getQuery().trim()); + assertEquals("Prop IN (5.0,6.0,7.0,8.0)", prop.in((Float) 5f, (Float) 6f, (Float) 7f, (Float) 8f).getQuery().trim()); + assertEquals("Prop NOT IN (5.0,6.0,7.0,8.0)", prop.notIn((Float) 5f, (Float) 6f, (Float) 7f, (Float) 8f).getQuery().trim()); + assertEquals("Prop=Prop + 5.0", prop.concatenate((Float) 5f).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..12a4975f1fdb5b9bcba5e1dcbf7672d3744a0089 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.java @@ -0,0 +1,25 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.SimpleModel_Table; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.property.IndexProperty; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IndexPropertyTest extends BaseUnitTest { + @Test + public void validateIndexProperty() { + FlowManager.databaseForTable(SimpleTestModels.SimpleModel.class, db -> { + IndexProperty prop = new IndexProperty<>("_Index", true, SimpleTestModels.SimpleModel.class, SimpleModel_Table.name); + prop.createIfNotExists(db); + prop.drop(db); + assertEquals("_Index", prop.indexName); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IntPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IntPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b38d9f9f219d736ce83192abf513a546a9878984 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/IntPropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IntPropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5", prop.is((int) 5).getQuery().trim()); + assertEquals("Prop=5", prop.eq((int) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.notEq((int) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.isNot((int) 5).getQuery().trim()); + assertEquals("Prop>5", prop.greaterThan((int) 5).getQuery().trim()); + assertEquals("Prop>=5", prop.greaterThanOrEq((int) 5).getQuery().trim()); + assertEquals("Prop<5", prop.lessThan((int) 5).getQuery().trim()); + assertEquals("Prop<=5", prop.lessThanOrEq((int) 5).getQuery().trim()); + assertEquals("Prop BETWEEN 5 AND 6", prop.between((int) 5).and((int) 6).getQuery().trim()); + assertEquals("Prop IN (5,6,7,8)", prop.in((int) 5, (int) 6, (int) 7, (int) 8).getQuery().trim()); + assertEquals("Prop NOT IN (5,6,7,8)", prop.notIn((int) 5, (int) 6, (int) 7, (int) 8).getQuery().trim()); + assertEquals("Prop=Prop + 5", prop.concatenate((int) 5).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/LongPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/LongPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6cacd4efc22ad1d8a38fdaa4df0c9b306d9016c5 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/LongPropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class LongPropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5", prop.is((long) 5).getQuery().trim()); + assertEquals("Prop=5", prop.eq((long) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.notEq((long) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.isNot((long) 5).getQuery().trim()); + assertEquals("Prop>5", prop.greaterThan((long) 5).getQuery().trim()); + assertEquals("Prop>=5", prop.greaterThanOrEq((long) 5).getQuery().trim()); + assertEquals("Prop<5", prop.lessThan((long) 5).getQuery().trim()); + assertEquals("Prop<=5", prop.lessThanOrEq((long) 5).getQuery().trim()); + assertEquals("Prop BETWEEN 5 AND 6", prop.between((long) 5).and((long) 6).getQuery().trim()); + assertEquals("Prop IN (5,6,7,8)", prop.in((long) 5, (long) 6, (long) 7, (long) 8).getQuery().trim()); + assertEquals("Prop NOT IN (5,6,7,8)", prop.notIn((long) 5, (long) 6, (long) 7, (long) 8).getQuery().trim()); + assertEquals("Prop=`Prop` + 5", prop.concatenate((long) 5).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1af4a817c000e0dbffbd8cd06f5c4db674038d98 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.java @@ -0,0 +1,27 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.PropertyFactory; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PropertyFactoryTest extends BaseUnitTest { + @Test + public void testPrimitives() { + assertEquals("'c'", PropertyFactory.from("c").getQuery()); + assertEquals("5", PropertyFactory.from(5).getQuery()); + assertEquals("5.0", PropertyFactory.from(5.0).getQuery()); + assertEquals("5.0", PropertyFactory.from(5f).getQuery()); + assertEquals("5", PropertyFactory.from(5L).getQuery()); + assertEquals("5", PropertyFactory.from((short) 5).getQuery()); + assertEquals("5", PropertyFactory.from((byte) 5).getQuery()); + Object nullable = null; + assertEquals("NULL", PropertyFactory.from(nullable).getQuery()); + assertEquals("(SELECT * FROM SimpleModel)", PropertyFactory.from((SQLite.select().from(SimpleTestModels.SimpleModel.class))).getQuery()); + assertEquals("SomethingCool", PropertyFactory.propertyString(String.class, "SomethingCool").getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c960b323f19b7377f38234aeb773f3e694cef30d --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/PropertyTest.java @@ -0,0 +1,45 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop='5'", prop.is("5").getQuery()); + assertEquals("Prop='5'", prop.eq("5").getQuery()); + assertEquals("Prop!='5'", prop.notEq("5").getQuery()); + assertEquals("Prop!='5'", prop.isNot("5").getQuery()); + assertEquals("Prop LIKE '5'", prop.like("5").getQuery()); + assertEquals("Prop NOT LIKE '5'", prop.notLike("5").getQuery()); + assertEquals("Prop GLOB '5'", prop.glob("5").getQuery()); + assertEquals("Prop >'5'", prop.greaterThan("5").getQuery()); + assertEquals("Prop >='5'", prop.greaterThanOrEq("5").getQuery()); + assertEquals("Prop <'5'", prop.lessThan("5").getQuery()); + assertEquals("Prop <='5'", prop.lessThanOrEq("5").getQuery()); + assertEquals("Prop BETWEEN '5' AND '6'", prop.between("5").and("6").getQuery()); + assertEquals("Prop IN ('5','6','7','8')", prop.in("5","6","7","8").getQuery()); + assertEquals("Prop NOT IN ('5','6','7','8')", prop.notIn("5","6","7","8").getQuery()); + assertEquals("Prop || '5'", prop.concatenate("5").getQuery()); + assertEquals("Prop MATCH 'age'", prop.match("age").getQuery()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..21f32696f8a740103b8f81e29f8db34cb5410828 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.java @@ -0,0 +1,41 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ShortPropertyTest extends BaseUnitTest { + @Test + public void testOperators() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop"); + assertEquals("Prop=5", prop.is((short) 5).getQuery().trim()); + assertEquals("Prop=5", prop.eq((short) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.notEq((short) 5).getQuery().trim()); + assertEquals("Prop!=5", prop.isNot((short) 5).getQuery().trim()); + assertEquals("Prop>5", prop.greaterThan((short) 5).getQuery().trim()); + assertEquals("Prop>=5", prop.greaterThanOrEq((short) 5).getQuery().trim()); + assertEquals("Prop<5", prop.lessThan((short) 5).getQuery().trim()); + assertEquals("Prop<=5", prop.lessThanOrEq((short) 5).getQuery().trim()); + assertEquals("Prop BETWEEN 5 AND 6", prop.between((short) 5).and((short) 6).getQuery().trim()); + assertEquals("Prop IN (5,6,7,8)", prop.in((short) 5, (short) 6, (short) 7, (short) 8).getQuery().trim()); + assertEquals("Prop NOT IN (5,6,7,8)", prop.notIn((short) 5, (short) 6, (short) 7, (short) 8).getQuery().trim()); + assertEquals("Prop=Prop + 5", prop.concatenate((short) 5).getQuery().trim()); + } + + @Test + public void testAlias() { + Property prop = new Property<>(SimpleTestModels.SimpleModel.class, "Prop", "Alias"); + assertEquals("Prop AS Alias", prop.toString().trim()); + Property prop2 = new Property<>(SimpleTestModels.SimpleModel.class, + NameAlias.builder("Prop").shouldAddIdentifierToName(false) + .as("Alias") + .shouldAddIdentifierToAliasName(false).build() + ); + assertEquals("Prop AS Alias", prop2.toString().trim()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.java b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..35c56ceeb7e0915ed4c1145a65dd1cc2503566f1 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.java @@ -0,0 +1,52 @@ +package com.dbflow5.sql.language.property; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.models.EnumTypeConverterModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.TypeConvertedProperty; + +import org.junit.Test; + +import java.util.Date; + +import static org.junit.Assert.assertEquals; + +public class TypeConvertedPropertyTest extends BaseUnitTest { + @Test + public void testTypeConverter() { + TypeConvertedProperty property = new TypeConvertedProperty<>(SimpleTestModels.SimpleModel.class, "Prop", true, new TypeConvertedProperty.TypeConverterGetter() { + @Override + public TypeConverters.TypeConverter getTypeConverter(Class modelClass) { + + return new TypeConverters.TypeConverter() { + @Override + public Long getDBValue(Date model) { + return model.getTime(); + } + + @Override + public Date getModelValue(Long data) { + return new Date(data); + } + }; + } + }); + assertEquals("Prop", property.toString()); + Date date = new Date(); + assertEquals("Prop=" + date.getTime(), property.eq(date).getQuery()); + assertEquals("SimpleModel.Prop=" + date.getTime(), property.withTable().eq(date).getQuery()); + + Property inverted = property.invertProperty(); + assertEquals("Prop=5050505", inverted.eq((long) 5050505).getQuery()); + } + + @Test + public void testCustomEnumTypeConverter(){ + assertEquals("difficulty='H'", EnumTypeConverterModel_Table.difficulty.eq(SimpleTestModels.Difficulty.HARD).getQuery()); + assertEquals("EnumTypeConverterModel.difficulty='H'", EnumTypeConverterModel_Table.difficulty.withTable().eq(SimpleTestModels.Difficulty.HARD).getQuery()); + assertEquals("et.difficulty='H'", EnumTypeConverterModel_Table.difficulty.withTable(NameAlias.tableNameBuilder("et").build()).eq(SimpleTestModels.Difficulty.HARD).getQuery()); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherDatabase.java b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..97da6a8a76a47741db6582330e05fb22894312e9 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherDatabase.java @@ -0,0 +1,14 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.annotation.Database; +import com.dbflow5.config.DBFlowDatabase; + +/** + * CipherDatabase + * + * @author wangyin + * @since 2021/06/21 + */ +@Database(version = 1) +public abstract class CipherDatabase extends DBFlowDatabase { +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherModel_Table.java b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherModel_Table.java new file mode 100644 index 0000000000000000000000000000000000000000..8503aab13748cbd3bc484789fe14f725ae670948 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherModel_Table.java @@ -0,0 +1,141 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ObjectType; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.sqlcipher.CipherTestObjects.CipherModel; +import java.lang.IllegalArgumentException; +import java.lang.Long; +import java.lang.Number; +import java.lang.Override; +import java.lang.String; + +public final class CipherModel_Table extends ModelAdapter { + /** + * Primary Key AutoIncrement */ + public static final Property id = new Property(CipherModel.class, "id"); + + public static final Property name = new Property(CipherModel.class, "name"); + + public static final IProperty[] ALL_COLUMN_PROPERTIES = new IProperty[]{id,name}; + + public CipherModel_Table(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } + + @Override + public final Class table() { + return CipherModel.class; + } + + @Override + public final String getName() { + return "CipherModel"; + } + + @Override + public final ObjectType getType() { + return ObjectType.Table; + } + + @Override + public final Property getProperty(String columnName) { + String columnName2 = StringUtils.quoteIfNeeded(columnName); + switch ((columnName2)) { + case "id": { + return id; + } + case "name": { + return name; + } + default: { + throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); + } + } + } + + @Override + public final void updateAutoIncrement(CipherModel model, Number id) { + model.setId(id.longValue()); + } + + @Override + public final IProperty[] getAllColumnProperties() { + return ALL_COLUMN_PROPERTIES; + } + + @Override + public final void bindToInsertStatement(DatabaseStatement statement, CipherModel model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + } + + @Override + public final void bindToUpdateStatement(DatabaseStatement statement, CipherModel model) { + statement.bindLong(1, model.getId()); + statement.bindStringOrNull(2, model.getName()); + statement.bindLong(3, model.getId()); + } + + @Override + public final void bindToDeleteStatement(DatabaseStatement statement, CipherModel model) { + statement.bindLong(1, model.getId()); + } + + @Override + public final String getInsertStatementQuery() { + return "INSERT INTO CipherModel(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getSaveStatementQuery() { + return "INSERT OR REPLACE INTO CipherModel(id,name) VALUES (nullif(?, 0),?)"; + } + + @Override + public final String getUpdateStatementQuery() { + return "UPDATE CipherModel SET id=?,name=? WHERE id=?"; + } + + @Override + public final String getDeleteStatementQuery() { + return "DELETE FROM CipherModel WHERE id=?"; + } + + @Override + public final String getCreationQuery() { + return "CREATE TABLE IF NOT EXISTS CipherModel(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"; + } + + @Override + public final CipherModel loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper) { + CipherModel model = new CipherModel(0, null); + model.setId(cursor.getLongOrDefault("id")); + model.setName(cursor.getStringOrDefault("name")); + return model; + } + + @Override + public final boolean exists(CipherModel model, DatabaseWrapper wrapper) { + return model.getId() > 0 + && SQLite.selectCountOf() + .from(CipherModel.class) + .where(getPrimaryConditionClause(model)) + .hasData(wrapper); + } + + @Override + public final OperatorGroup getPrimaryConditionClause(CipherModel model) { + OperatorGroup clause = OperatorGroup.clause(); + clause.and(id.eq(model.getId())); + return clause; + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTest.java b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ad5146dc8450489638ed91975cff7c102f82125c --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTest.java @@ -0,0 +1,43 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.DBFlowInstrumentedTestRule; +import com.dbflow5.DemoApp; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.SQLite; +import com.dbflow5.structure.Model; + +import ohos.aafwk.ability.DataAbilityRemoteException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CipherTest { + public DBFlowInstrumentedTestRule dblflowTestRule; + + public CipherTest() { + dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> { + builder.database(CipherDatabase.class, builder12 -> null, SQLCipherOpenHelper.createHelperCreator(DemoApp.context, "dbflow-rules")); + return null; + }); + } + + @Test + public void testCipherModel(){ + FlowManager.database(CipherDatabase.class, cipherDatabase -> { + SQLite.delete().from(CipherTestObjects.CipherModel.class).execute(cipherDatabase); + CipherTestObjects.CipherModel model = new CipherTestObjects.CipherModel(0L, "name"); + Model.save(Object.class,model, cipherDatabase); + try { + assertTrue(model.exists(cipherDatabase)); + } catch (DataAbilityRemoteException e) { + e.printStackTrace(); + } + + CipherTestObjects.CipherModel retrieval = SQLite.select().from(CipherTestObjects.CipherModel.class).where(CipherModel_Table.name.eq("name")).querySingle(cipherDatabase); + assertEquals(retrieval.id, model.id); + SQLite.delete().from(CipherTestObjects.CipherModel.class).execute(cipherDatabase); + return null; + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTestObjects.java b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTestObjects.java new file mode 100644 index 0000000000000000000000000000000000000000..fa0748162f857b6a912cbb683c9251d33702aa0a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/sqlcipher/CipherTestObjects.java @@ -0,0 +1,59 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseCallback; +import com.dbflow5.structure.BaseModel; +import ohos.app.Context; + +/** + * CipherTestObjects + * + * @author wangyin + * @since 2021/06/21 + */ +public class CipherTestObjects { + + public static class SQLCipherOpenHelperImpl extends SQLCipherOpenHelper { + public Context context; + public DBFlowDatabase databaseDefinition; + public DatabaseCallback callback; + + public SQLCipherOpenHelperImpl(Context context, DBFlowDatabase databaseDefinition, DatabaseCallback listener, Context context1, DBFlowDatabase databaseDefinition1, DatabaseCallback callback) { + super(context, databaseDefinition, listener); + this.context = context1; + this.databaseDefinition = databaseDefinition1; + this.callback = callback; + cipherSecret = "dbflow-rules"; + } + } + + public static class CipherModel extends BaseModel { + @PrimaryKey + public long id = 0; + @Column + public String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CipherModel(long id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/test/ExampleOhosTest.java b/entry/src/ohosTest/java/com/dbflow5/test/ExampleOhosTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eb1b6f9846fe963c36236663bb834442b951c185 --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/test/ExampleOhosTest.java @@ -0,0 +1,14 @@ +package com.dbflow5.test; + +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExampleOhosTest { + @Test + public void testBundleName() { + final String actualBundleName = AbilityDelegatorRegistry.getArguments().getTestBundleName(); + assertEquals("com.dbflow5", actualBundleName); + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/dbflow5/transaction/CoroutinesTest.java b/entry/src/ohosTest/java/com/dbflow5/transaction/CoroutinesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..64e2e542fa049d5b0ad845892db837906fb4518a --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/transaction/CoroutinesTest.java @@ -0,0 +1,232 @@ +package com.dbflow5.transaction; + +import com.dbflow5.BaseUnitTest; +import com.dbflow5.TestDatabase; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.models.SimpleModel_Table; +import com.dbflow5.models.SimpleTestModels; +import com.dbflow5.models.TwoColumnModel_Table; +import com.dbflow5.query.SQLite; +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.schedulers.Schedulers; +import org.junit.Test; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.List; +import java.util.function.Function; + +public class CoroutinesTest extends BaseUnitTest { + @Test + public void testTransact() { + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + ModelAdapter modelAdapter = FlowManager.getModelAdapter(SimpleTestModels.SimpleModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + for (int i = 0; i < 9; i++) { + SimpleTestModels.SimpleModel simpleModel = new SimpleTestModels.SimpleModel("$it"); + modelAdapter.save(simpleModel, db); + } + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + + @Override + public void onNext(Integer integer) { + + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + SQLite.select().from(SimpleTestModels.SimpleModel.class).where(SimpleModel_Table.name.eq("5")); + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + List query = SQLite.select().from(SimpleTestModels.SimpleModel.class).where(SimpleModel_Table.name.eq("5")).queryList(databaseWrapper); + assert (query.size() == 1); + SQLite.update(SimpleTestModels.SimpleModel.class).set(SimpleModel_Table.name.eq("5")).executeUpdateDelete(db); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } + + @Test + public void testAwaitSaveAndDelete() { + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + ModelAdapter modelAdapter = FlowManager.getModelAdapter(SimpleTestModels.SimpleModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + SimpleTestModels.SimpleModel simpleModel = new SimpleTestModels.SimpleModel("Name"); + modelAdapter.save(simpleModel, db); + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + + @Override + public void onNext(Integer integer) { + + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + List result = null; + result = SQLite.select().from(SimpleTestModels.SimpleModel.class).queryList(databaseWrapper); + assert (result.size() == 0); + assert (modelAdapter.delete(SimpleTestModels.SimpleModel.class, db)); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } + + @Test + public void testAwaitInsertAndDelete() { + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + ModelAdapter modelAdapter = FlowManager.getModelAdapter(SimpleTestModels.SimpleModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + SimpleTestModels.SimpleModel simpleModel = new SimpleTestModels.SimpleModel("Name"); + modelAdapter.insert(simpleModel, db); + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + + @Override + public void onNext(Integer integer) { + + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + List result; + result = SQLite.select().from(SimpleTestModels.SimpleModel.class).queryList(databaseWrapper); + assert (result.size() > 0); + assert (modelAdapter.delete(SimpleTestModels.SimpleModel.class, db)); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } + + @Test + public void testAwaitUpdate() { + TestDatabase db = FlowManager.getDatabase(TestDatabase.class); + ModelAdapter modelAdapter = FlowManager.getModelAdapter(SimpleTestModels.TwoColumnModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + SimpleTestModels.TwoColumnModel simpleModel = new SimpleTestModels.TwoColumnModel("Name", 5); + modelAdapter.update(simpleModel, db); + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + + @Override + public void onNext(Integer integer) { + + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + List result = null; + result = SQLite.select().from(SimpleTestModels.TwoColumnModel.class).queryList(databaseWrapper); + assert (result.size() == 0); + SimpleTestModels.TwoColumnModel loadedModel = SQLite.select().from(SimpleTestModels.TwoColumnModel.class).where(TwoColumnModel_Table.id.eq(5)).querySingle(databaseWrapper); + assert (loadedModel.id == 5); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } +} diff --git a/entry/src/ohosTest/java/com/dbflow5/transaction/FastStoreModelTransactionTest.java b/entry/src/ohosTest/java/com/dbflow5/transaction/FastStoreModelTransactionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..375c2dbc76827c6667b19ae083b15e900553744e --- /dev/null +++ b/entry/src/ohosTest/java/com/dbflow5/transaction/FastStoreModelTransactionTest.java @@ -0,0 +1,184 @@ +package com.dbflow5.transaction; + +import com.dbflow5.TestDatabase; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.SQLite; +import com.dbflow5.BaseUnitTest; +import com.dbflow5.models.SimpleTestModels; +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.FlowableOnSubscribe; +import io.reactivex.rxjava3.schedulers.Schedulers; +import org.junit.Assert; +import org.junit.Test; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; + +public class FastStoreModelTransactionTest extends BaseUnitTest { + @Test + public void testSaveBuilder() { + TestDatabase db= FlowManager.getDatabase(TestDatabase.class); + ModelAdapter adapter=FlowManager.getModelAdapter(SimpleTestModels.SimpleModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + for (int i = 0; i < 9; i++) { + SimpleTestModels.SimpleModel simpleModel=new SimpleTestModels.SimpleModel("$it"); + adapter.save(simpleModel,db); + } + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + @Override + public void onNext(Integer integer) { + + } + @Override + public void onError(Throwable t) { + + } + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + Listresult= SQLite.select().from(SimpleTestModels.SimpleModel.class).queryList(databaseWrapper); + assertEquals(10, result.size()); + assertEquals(10L, result); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } + + @Test + public void testInsertBuilder() { + TestDatabase db= FlowManager.getDatabase(TestDatabase.class); + ModelAdapter adapter=FlowManager.getModelAdapter(SimpleTestModels.SimpleModel.class); + Flowable.create((FlowableOnSubscribe) e -> { + for (int i = 0; i < 9; i++) { + SimpleTestModels.SimpleModel simpleModel=new SimpleTestModels.SimpleModel("$it"); + adapter.insert(simpleModel,db); + } + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + @Override + public void onNext(Integer integer) { + + } + @Override + public void onError(Throwable t) { + + } + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + Listlist= SQLite.select().from(SimpleTestModels.SimpleModel.class).queryList(databaseWrapper); + assertEquals(10, list.size()); + assertEquals(10L, list); + return null; + } + + @Override + public Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + + } + + @Test + public void testUpdateBuilder() { + TestDatabase db= FlowManager.getDatabase(TestDatabase.class); + ModelAdapter adapter=FlowManager.getModelAdapter(SimpleTestModels.TwoColumnModel.class); + List oldList=new ArrayList<>(); + Flowable.create((FlowableOnSubscribe) e -> { + for (int i = 0; i < 9; i++) { + Random random=new Random(); + SimpleTestModels.TwoColumnModel twoColumnModel=new SimpleTestModels.TwoColumnModel("$it",random.nextInt()); + oldList.add(twoColumnModel); + adapter.update(twoColumnModel,db); + } + }, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); //观察者设置接收事件的数量,如果不设置接收不到事件 + } + @Override + public void onNext(Integer integer) { + + } + @Override + public void onError(Throwable t) { + + } + @Override + public void onComplete() { + db.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + Listlist=SQLite.select().from(SimpleTestModels.TwoColumnModel.class).queryList(databaseWrapper); + assertEquals(10, list.size()); + if(list.size()>0){ + for (int i = 0; i Function compose(Function before) { + return null; + } + + @Override + public Function andThen(Function after) { + return null; + } + }); + } + }); + } +} diff --git a/entry/src/test/java/com/dbflow5/test/ExampleTest.java b/entry/src/test/java/com/dbflow5/test/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e6971d04dcfbc188e9e1f953b6c9da99a4bd6571 --- /dev/null +++ b/entry/src/test/java/com/dbflow5/test/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.test; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/gettingstarted.md b/gettingstarted.md deleted file mode 100644 index 3c185ec49c4d73205f8320d191f0d8dbb6a16f3d..0000000000000000000000000000000000000000 --- a/gettingstarted.md +++ /dev/null @@ -1,223 +0,0 @@ -# GettingStarted - -This section describes how Models and tables are constructed via DBFlow. first let's describe how to get a database up and running. - -## Creating a Database - -In DBFlow, creating a database is as simple as only a few lines of code. DBFlow supports any number of databases, however individual tables and other related files can only be associated with one database. - -```kotlin -@Database(version = 1) -abstract class AppDatabase : DBFlowDatabase() -``` - -```java -@Database(version = 1) -public abstract class AppDatabase extends DBFlowDatabase { -} -``` - -The name of the database by default is the class name. To change it, read [here](usage2/usage/databases.md). - -Writing this file generates \(by default\) a `AppDatabaseAppDatabase_Database.java` file, which contains tables, views, and more all tied to a specific database. This class is automatically placed into the main `GeneratedDatabaseHolder`, which holds potentially many databases. The name, `AppDatabaseAppDatabase_Database.java`, is generated via {DatabaseClassName}{DatabaseFileName}\_Database - -To learn more about what you can configure in a database, read [here](usage2/usage/databases.md) - -## Initialize FlowManager - -DBFlow currently needs an instance of `Context` in order to use it for a few features such as reading from assets, content observing, and generating `ContentProvider`. - -Initialize in your `Application` subclass. You can also initialize it from other `Context` but we always grab the `Application` `Context` \(this is done only once\). - -```kotlin -class ExampleApplication : Application { - - override fun onCreate() { - super.onCreate() - FlowManager.init(this) - } -} -``` - -```java -public class ExampleApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FlowManager.init(this); - } -} -``` - -By default without passing in a `DatabaseConfig`, we construct an `AndroidSQLiteOpenHelper` database instance. To learn more about what you can configure in a database, read [here](usage2/usage/databases.md), including providing own database instances. - -Finally, add the custom `Application` definition to the manifest \(with the name that you chose for the class\): - -```markup - - -``` - -A database within DBFlow is only initialized once you call `database()`. If you don't want this behavior or prefer it to happen immediately, modify your `FlowConfig`: - -```kotlin -override fun onCreate() { - super.onCreate() - FlowManager.init(FlowConfig.builder(this) - .openDatabasesOnInit(true) - .build()) -} -``` - -```java -@Override -public void onCreate() { - super.onCreate(); - FlowManager.init(FlowConfig.builder(this) - .openDatabasesOnInit(true) - .build()); -} -``` - -If you do not like the built-in `DefaultTransactionManager`, or just want to roll your own existing system: - -```kotlin -FlowManager.init(FlowConfig.builder(this) - .database(DatabaseConfig.builder(AppDatabase::class) - .transactionManagerCreator { db -> CustomTransactionManager(db)) - .build())) -``` - -You can define different kinds for each database. To read more on transactions and subclassing `BaseTransactionManager` go [here](usage2/usage/storingdata.md) - -## Create Models - -Creating models are as simple as defining the model class, and adding the `@Table` annotation. To read more on this, read [here](usage2/usage/models.md). - -**For now**: Models must provide a default, parameterless constructor. Also, all fields must be mutable \(currently, we hope to evolve this requirement in the near future\). An example: - -```kotlin -@Table(database = TestDatabase::class) - class Currency(@PrimaryKey(autoincrement = true) var id: Long = 0, - @Column @Unique var symbol: String? = null, - @Column var shortName: String? = null, - @Column @Unique var name: String = "") // nullability of fields are respected. We will not assign a null value to this field. -``` - -or with Java: - -```java -@Table(database = TestDatabase.class) -public class Currency { - - @PrimaryKey(autoincrement = true) - long id; // package-private recommended, not required - - @Column - @Unique - String symbol; - - @Column - String shortName; - - @Column - @Unique - private String name; // private with getters and setters - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} -``` - -## Build Your DAO - -Set up a DAO \(Data Access Object\) to help you interact with your database. Using dependency injection and service locating, we can build better, highly testable code. While not required to use DBFlow, it is **highly** recommended utilize this approach. - -With kotlin, we can utilize it in a powerful way: - -```kotlin -/** -* Create this class in your own database module. -*/ -interface DBProvider { - - val database: T - -} - -interface CurrencyDAO : DBProvider { - - /** - * Utilize coroutines package - */ - fun coroutineRetrieveUSD(): Deferred> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")).queryList(it) - }.defer() - - /** - * Utilize RXJava2 package. - * Also can use asMaybe(), or asFlowable() (to register for changes and continue listening) - */ - fun rxRetrieveUSD(): Single> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - }.asSingle() - - /** - * Utilize Vanilla Transactions. - */ - fun retrieveUSD(): Transaction.Builder> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - } - - /** - * Utilize Paging Library from paging artifact. - */ - fun pagingRetrieveUSD(): QueryDataSource.Factory> = (select from Currency::class - where (Currency_Table.symbol eq "$")) - .toDataSourceFactory(database) - -} -``` - -DBFlow uses expressive builders to represent and translate to the SQLite language. - -We can represent the query above in SQLite: - -```text -SELECT * FROM Currency WHERE symbol='$'; -``` - -Wherever we perform dependency injection we supply the instance: - -```kotlin -fun provideCurrencyDAO(db: AppDatabase) = object : CurrencyDAO { - override val database: AppDatabase = db -} -``` - -Then in our `ViewModel`, we can inject it via the constructor and utilize it in our queries: - -```kotlin -class SampleViewModel(private currencyDAO: CurrencyDAO) -``` - -We support many kinds of complex and complicated queries using the builder language. To read more about this, see [the wrapper language docs](usage2/usage/sqlitewrapperlanguage.md) - -There is much more you can do in DBFlow. Read through the other docs to get a sense of the library. - diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 7d9d873ad0ad635fcad365ada3df4b63e0843ff9..0000000000000000000000000000000000000000 --- a/gradle.properties +++ /dev/null @@ -1,29 +0,0 @@ -## 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. -# Default value: -Xmx1024m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -# -# 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 -#Sat Jan 30 13:55:08 EST 2021 -android.enableJetifier=true -android.useAndroidX=true -bt_gitUrl=https\://github.com/agrosner/DBFlow.git -bt_licenseName=The MIT License -bt_licenseUrl=http\://opensource.org/licenses/MIT -bt_repo=Libraries -bt_siteUrl=https\://github.com/agrosner/DBFlow -dbflow_build_tools_version=26.0.2 -dbflow_min_sdk=4 -dbflow_min_sdk_rx=15 -dbflow_project_prefix=\: -dbflow_target_sdk=26 -group=com.dbflow5 -kotlin.incremental=false -version=5.0.0-alpha2 -version_code=1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2434b1a1ec503eb767f61dc0a2044ba1c3896046..f59159e865d4b59feb1b8c44b001f62fc5d58df4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sat Jan 30 13:51:40 EST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/gradlew b/gradlew deleted file mode 100755 index 91a7e269e19dfc62e27137a0b57ef3e430cee4fd..0000000000000000000000000000000000000000 --- a/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# 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 -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# 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\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -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" ] ; 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"` - - # 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 - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/issue_template.md b/issue_template.md deleted file mode 100644 index 51b578b33d59ea561a439ba4f31fde7ac09167da..0000000000000000000000000000000000000000 --- a/issue_template.md +++ /dev/null @@ -1,8 +0,0 @@ -# ISSUE\_TEMPLATE - -DBFlow Version: - -Bug or Feature Request: - -Description: - diff --git a/java-artifacts.gradle b/java-artifacts.gradle deleted file mode 100644 index 0dfd8047b4f14ff90ae867bf109f542e9b03c979..0000000000000000000000000000000000000000 --- a/java-artifacts.gradle +++ /dev/null @@ -1,39 +0,0 @@ -apply plugin: 'com.github.dcendents.android-maven' - -install { - repositories.mavenInstaller { - pom { - project { - packaging bt_packaging - name bt_name - url bt_siteUrl - licenses { - license { - name bt_licenseName - url bt_licenseUrl - } - } - scm { - connection bt_gitUrl - developerConnection bt_gitUrl - url bt_siteUrl - } - } - } - } -} - -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar - archives javadocJar -} \ No newline at end of file diff --git a/kotlin-artifacts.gradle b/kotlin-artifacts.gradle deleted file mode 100644 index 38f9f6cb5bac4a09446f163b5f6fed80c9dc4bc7..0000000000000000000000000000000000000000 --- a/kotlin-artifacts.gradle +++ /dev/null @@ -1,33 +0,0 @@ -apply plugin: 'com.github.dcendents.android-maven' - -install { - repositories.mavenInstaller { - pom { - project { - packaging bt_packaging - name bt_name - url bt_siteUrl - licenses { - license { - name bt_licenseName - url bt_licenseUrl - } - } - scm { - connection bt_gitUrl - developerConnection bt_gitUrl - url bt_siteUrl - } - } - } - } -} - -task sourcesJar(type: Jar) { - from "src" - classifier = "sources" -} - -artifacts { - archives sourcesJar -} \ No newline at end of file diff --git a/tests/.gitignore b/lib/.gitignore similarity index 100% rename from tests/.gitignore rename to lib/.gitignore diff --git a/lib/build.gradle b/lib/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..63ff6dbf2d6d416f589603d1cfb560f2b4dcba80 --- /dev/null +++ b/lib/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.huawei.ohos.library' +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + api project(path: ':core') +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" \ No newline at end of file diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts deleted file mode 100644 index 890e0601beffac82e581bf6802776b1311b92c79..0000000000000000000000000000000000000000 --- a/lib/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.MinSdk) - targetSdkVersion(Versions.TargetSdk) - } - - lintOptions { - isAbortOnError = false - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } - - kotlinOptions { - freeCompilerArgs = listOf("-Xinline-classes") - } -} - -dependencies { - api(project(":core")) - api(Dependencies.AndroidX.Annotations) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/lib/gradle.properties b/lib/gradle.properties deleted file mode 100644 index f02d9329c89341f585dcd7de20cd8cbbacef90a0..0000000000000000000000000000000000000000 --- a/lib/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow -bt_packaging=aar -bt_artifact_id=dbflow \ No newline at end of file diff --git a/lib/proguard-rules.pro b/lib/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/lib/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/lib/settings.gradle b/lib/settings.gradle deleted file mode 100644 index 00126c83cd98c292680fc4b1685a6e5d292fb7c1..0000000000000000000000000000000000000000 --- a/lib/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = buildName \ No newline at end of file diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml deleted file mode 100644 index 1c6093f3aea329239e750d6659b3badcb6b09088..0000000000000000000000000000000000000000 --- a/lib/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/lib/src/main/config.json b/lib/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..f56c5a0ad972102caff49cb507a8bd2ed462b0a8 --- /dev/null +++ b/lib/src/main/config.json @@ -0,0 +1,28 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + }, + "apiVersion": { + "compatible": 5, + "target": 5, + "releaseType": "Release" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.adapter", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "lib", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/DatabaseFileUtils.java b/lib/src/main/java/com/dbflow5/DatabaseFileUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..09bf318b32f283f4978d03bdb9376eb592971cd1 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/DatabaseFileUtils.java @@ -0,0 +1,18 @@ +package com.dbflow5; + +import ohos.app.Context; + +import java.io.File; + +public class DatabaseFileUtils { + + public static File getDatabasePath(Context context, String databaseName){ + return new File(context.getDatabaseDir(), databaseName); + } + + public static boolean deleteDatabase(Context context, String databaseName) { + File dbFile = getDatabasePath(context, databaseName); + return dbFile.delete(); + } + +} diff --git a/lib/src/main/java/com/dbflow5/SqlUtils.java b/lib/src/main/java/com/dbflow5/SqlUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2f5fc91fbf080f2a2fc5df9ead4833cffd28381c --- /dev/null +++ b/lib/src/main/java/com/dbflow5/SqlUtils.java @@ -0,0 +1,206 @@ +package com.dbflow5; + +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.NameAlias; +import com.dbflow5.query.Operator; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLOperator; +import com.dbflow5.structure.ChangeAction; +import ohos.data.rdb.ValuesBucket; +import ohos.utils.net.Uri; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +/** + * Description: Provides some handy methods for dealing with SQL statements. It's purpose is to move the + * methods away from the [Model] class and let any class use these. + */ +public class SqlUtils{ + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static final String TABLE_QUERY_PARAM = "tableName"; + + /** + * Constructs a [Uri] from a set of [SQLOperator] for specific table. + * + * @param modelClass The class of table, + * @param action The action to use. + * @param conditions The set of key-value [SQLOperator] to construct into a uri. + * @return The [Uri]. + */ + public static Uri getNotificationUri(String contentAuthority, + Class modelClass, + ChangeAction action, + Iterable conditions) { + Uri.Builder uriBuilder = new Uri.Builder().scheme("dbflow") + .decodedAuthority(contentAuthority) + .appendDecodedQueryParam(TABLE_QUERY_PARAM, FlowManager.getTableName(modelClass)); + if (action != null) { + uriBuilder.decodedFragment(action.name()); + } + if (conditions != null) { + for (SQLOperator condition : conditions) { + uriBuilder.appendDecodedQueryParam(Uri.encode(condition.columnName()), Uri.encode(condition.value().toString())); + } + } + return uriBuilder.build(); + } + + + /** + * Constructs a [Uri] from a set of [SQLOperator] for specific table. + * + * @param modelClass The class of table, + * @param action The action to use. + * @param conditions The set of key-value [SQLOperator] to construct into a uri. + * @return The [Uri]. + */ + public static Uri getNotificationUri(String contentAuthority, + Class modelClass, + ChangeAction action, + SQLOperator[] conditions) { + Uri.Builder uriBuilder = new Uri.Builder().scheme("dbflow") + .decodedAuthority(contentAuthority) + .appendDecodedQueryParam(TABLE_QUERY_PARAM, FlowManager.getTableName(modelClass)); + if (action != null) { + uriBuilder.decodedFragment(action.name()); + } + if (conditions != null && conditions.length > 0) { + for (SQLOperator condition : conditions) { + uriBuilder.appendDecodedQueryParam(Uri.encode(condition.columnName()), + Uri.encode(condition.value().toString())); + } + } + return uriBuilder.build(); + } + + /** + * Returns the uri for notifications from model changes + * + * @param modelClass The class to get table from. + * @param action the action changed. + * @param notifyKey The column key. + * @param notifyValue The column value that gets turned into a String. + * @return Notification uri. + */ + public static Uri getNotificationUri(String contentAuthority, + Class modelClass, + ChangeAction action, + String notifyKey, + Object notifyValue) { + Operator operator = null; + if (StringUtils.isNotNullOrEmpty(notifyKey)) { + operator = Operator.op(new NameAlias.Builder(notifyKey).build()).value(notifyValue); + } + return getNotificationUri(contentAuthority, modelClass, action, operator != null? new ArrayList<>((Collection) operator) : null); + } + + + /** + * Drops an active TRIGGER by specifying the onTable and triggerName + * + * @param databaseWrapper DatabaseWrapper + * @param triggerName The name of the trigger + */ + public static void dropTrigger(DatabaseWrapper databaseWrapper, String triggerName) { + databaseWrapper.execSQL("DROP TRIGGER IF EXISTS " + triggerName); + } + + /** + * Drops an active INDEX by specifying the onTable and indexName + * + * @param indexName The name of the index. + */ + public static void dropIndex(DatabaseWrapper databaseWrapper, String indexName) { + databaseWrapper.execSQL("DROP INDEX IF EXISTS ${indexName.quoteIfNeeded()}"); + } + + /** + * Adds [ContentValues] to the specified [OperatorGroup]. + * + * @param contentValues The content values to convert. + * @param operatorGroup The group to put them into as [Operator]. + */ + public static void addContentValues(ValuesBucket contentValues, OperatorGroup operatorGroup) { + Set entries = contentValues.getColumnSet(); + + for (String key : entries) { + operatorGroup.and(Operator.op(new NameAlias.Builder(key).build()).is(contentValues.getObject(key))); + } + } + + /** + * @param contentValues The object to check existence of. + * @param key The key to check. + * @return The key, whether it's quoted or not. + */ + public static String getContentValuesKey(ValuesBucket contentValues, String key) { + String quoted = StringUtils.quoteIfNeeded(key); + String result; + if(contentValues.hasColumn(quoted)){ + return quoted == null? "" : quoted; + }else { + String stripped = StringUtils.stripQuotes(key); + if(contentValues.hasColumn(stripped)){ + return stripped == null? "" : stripped; + }else { + throw new IllegalArgumentException("Could not find the specified key in the Content Values object."); + } + } + } + + public static long longForQuery(DatabaseWrapper wrapper, String query) { + DatabaseStatement statement = wrapper.compileStatement(query); + return statement.simpleQueryForLong(); + } + + public static String stringForQuery(DatabaseWrapper wrapper, String query) { + DatabaseStatement statement = wrapper.compileStatement(query); + return statement.simpleQueryForString(); + } + + public static double doubleForQuery(DatabaseWrapper wrapper, String query) { + DatabaseStatement statement = wrapper.compileStatement(query); + return (double) statement.simpleQueryForLong(); + } + + /** + * Converts a byte[] to a String hex representation for within wrapper queries. + */ + public static String byteArrayToHexString(byte[] bytes) { + if (bytes == null) return ""; + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = (int) bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + public static String sqlEscapeString(String value) { + StringBuilder buildString = new StringBuilder(); + appendEscapedSQLString(buildString, value); + return buildString.toString(); + } + + public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { + sb.append('\''); + if (sqlString.indexOf('\'') != -1) { + int length = sqlString.length(); + for (int i = 0;i < length; i++) { + char c = sqlString.charAt(i); + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } + } else + sb.append(sqlString); + sb.append('\''); + } +} diff --git a/lib/src/main/java/com/dbflow5/TriFunction.java b/lib/src/main/java/com/dbflow5/TriFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..3ce1984347ef6990e163215106e9e829f376e6cc --- /dev/null +++ b/lib/src/main/java/com/dbflow5/TriFunction.java @@ -0,0 +1,6 @@ +package com.dbflow5; + +@FunctionalInterface +public interface TriFunction { + R apply(T var1, U var2, V var3); +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/UriMatcher.java b/lib/src/main/java/com/dbflow5/UriMatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..abb58391d94d4a0acd968e6d55f075655956f5c6 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/UriMatcher.java @@ -0,0 +1,149 @@ +package com.dbflow5; + +import ohos.utils.net.Uri; + +import java.util.ArrayList; +import java.util.List; + +public class UriMatcher { + public static final int NO_MATCH = -1; + + /** + * Creates the root node of the URI tree. + * + * @param code the code to match for the root URI + */ + public UriMatcher(int code) { + mCode = code; + mWhich = -1; + mChildren = new ArrayList(); + mText = null; + } + + private UriMatcher(int which, String text) { + mCode = NO_MATCH; + mWhich = which; + mChildren = new ArrayList(); + mText = text; + } + + public void addURI(String authority, String path, int code) { + if (code < 0) { + throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); + } + + String[] tokens = null; + if (path != null) { + String newPath = path; + // Strip leading slash if present. + if (path.length() > 1 && path.charAt(0) == '/') { + newPath = path.substring(1); + } + tokens = newPath.split("/"); + } + + int numTokens = tokens != null ? tokens.length : 0; + UriMatcher node = this; + for (int i = -1; i < numTokens; i++) { + String token = i < 0 ? authority : tokens[i]; + ArrayList children = node.mChildren; + int numChildren = children.size(); + UriMatcher child; + int j; + for (j = 0; j < numChildren; j++) { + child = children.get(j); + if (token.equals(child.mText)) { + node = child; + break; + } + } + if (j == numChildren) { + // Child not found, create it + child = createChild(token); + node.mChildren.add(child); + node = child; + } + } + node.mCode = code; + } + + private static UriMatcher createChild(String token) { + switch (token) { + case "#": + return new UriMatcher(NUMBER, "#"); + case "*": + return new UriMatcher(TEXT, "*"); + default: + return new UriMatcher(EXACT, token); + } + } + + /** + * Try to match against the path in a url. + * + * @param uri The url whose path we will match against. + * @return The code for the matched node (added using addURI), + * or -1 if there is no matched node. + */ + public int match(Uri uri) { + final List pathSegments = uri.getDecodedPathList(); + final int li = pathSegments.size(); + + UriMatcher node = this; + + if (li == 0 && uri.getDecodedAuthority() == null) { + return this.mCode; + } + + for (int i = -1; i < li; i++) { + String u = i < 0 ? uri.getDecodedAuthority() : pathSegments.get(i); + ArrayList list = node.mChildren; + if (list == null) { + break; + } + node = null; + int lj = list.size(); + for (int j = 0; j < lj; j++) { + UriMatcher n = list.get(j); + which_switch: + switch (n.mWhich) { + case EXACT: + if (n.mText.equals(u)) { + node = n; + } + break; + case NUMBER: + int lk = u.length(); + for (int k = 0; k < lk; k++) { + char c = u.charAt(k); + if (c < '0' || c > '9') { + break which_switch; + } + } + node = n; + break; + case TEXT: + node = n; + break; + } + if (node != null) { + break; + } + } + if (node == null) { + return NO_MATCH; + } + } + + return node.mCode; + } + + private static final int EXACT = 0; + private static final int NUMBER = 1; + private static final int TEXT = 2; + + private int mCode; + private final int mWhich; + private final String mText; + private final ArrayList mChildren; +} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/CacheAdapter.kt b/lib/src/main/java/com/dbflow5/adapter/CacheAdapter.java similarity index 40% rename from lib/src/main/kotlin/com/dbflow5/adapter/CacheAdapter.kt rename to lib/src/main/java/com/dbflow5/adapter/CacheAdapter.java index cff856a33cc517f217baa1e3a820a60bccf2bbb4..a04208d411b7753becf60d37ea083576fe338389 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/CacheAdapter.kt +++ b/lib/src/main/java/com/dbflow5/adapter/CacheAdapter.java @@ -1,29 +1,39 @@ -package com.dbflow5.adapter +package com.dbflow5.adapter; -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.cache.MultiKeyCacheConverter -import com.dbflow5.query.cache.ModelCache -import com.dbflow5.structure.InvalidDBConfiguration +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.cache.MultiKeyCacheConverter; +import com.dbflow5.query.cache.ModelCache; +import com.dbflow5.structure.InvalidDBConfiguration; + +import java.util.Collection; /** * Description: */ -abstract class CacheAdapter(val modelCache: ModelCache, - val cachingColumnSize: Int = 1, - val cacheConverter: MultiKeyCacheConverter<*>? = null) { +public abstract class CacheAdapter{ + + public ModelCache modelCache; + public int cachingColumnSize = 1; + public MultiKeyCacheConverter cacheConverter = null; + + public CacheAdapter(ModelCache modelCache, int cachingColumnSize, MultiKeyCacheConverter cacheConverter){ + this.modelCache = modelCache; + this.cachingColumnSize = cachingColumnSize; + this.cacheConverter = cacheConverter; + } /** * @param cursor The cursor to load caching id from. * @return The single cache column from cursor (if single). */ - open fun getCachingColumnValueFromCursor(cursor: FlowCursor): Any? = Unit + public abstract Object getCachingColumnValueFromCursor(FlowCursor cursor); /** * @param model The model to load cache column data from. * @return The single cache column from model (if single). */ - open fun getCachingColumnValueFromModel(model: T): Any? = Unit + public abstract Object getCachingColumnValueFromModel(T model); /** * Loads all primary keys from the [FlowCursor] into the inValues. The size of the array must @@ -33,7 +43,7 @@ abstract class CacheAdapter(val modelCache: ModelCache, * @param cursor The cursor to load from. * @return The populated set of values to load from cache. */ - open fun getCachingColumnValuesFromCursor(inValues: Array, cursor: FlowCursor): Array? = null + public abstract Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor); /** * Loads all primary keys from the [TModel] into the inValues. The size of the array must @@ -44,41 +54,52 @@ abstract class CacheAdapter(val modelCache: ModelCache, * @param TModel The model to load from. * @return The populated set of values to load from cache. */ - open fun getCachingColumnValuesFromModel(inValues: Array, TModel: T): Array? = null + public abstract Object[] getCachingColumnValuesFromModel(Object[] inValues, T TModel); - fun storeModelInCache(model: T) { - modelCache.addModel(getCachingId(model), model) + public void storeModelInCache(T model) { + modelCache.addModel(getCachingId(model), model); } - fun storeModelsInCache(models: Collection) { - models.onEach { storeModelInCache(it) } + public void storeModelsInCache(Collection models) { + for(T t: models){ + storeModelInCache(t); + } } - fun removeModelFromCache(model: T) { - getCachingId(model)?.let { modelCache.removeModel(it) } + public void removeModelFromCache(T model) { + Object obj = getCachingId(model); + modelCache.removeModel(obj); } - fun removeModelsFromCache(models: Collection) { - models.onEach { removeModelFromCache(it) } + public void removeModelsFromCache(Collection models) { + for(T t : models){ + removeModelFromCache(t); + } } - fun clearCache() { - modelCache.clear() + public void clearCache() { + modelCache.clear(); } - fun getCachingId(inValues: Array?): Any? = when { - inValues?.size == 1 -> // if it exists in cache no matter the query we will use that one - inValues.getOrNull(0) - inValues != null -> cacheConverter?.getCachingKey(inValues) - ?: throw InvalidDBConfiguration("For multiple primary keys, a public static MultiKeyCacheConverter field must" + + public Object getCachingId(Object[] inValues) { + if(inValues != null){ + if(inValues.length == 1){ + return inValues[0]; + } + if(cacheConverter.getCachingKey(inValues) != null){ + return cacheConverter.getCachingKey(inValues); + }else { + throw new InvalidDBConfiguration("For multiple primary keys, a public static MultiKeyCacheConverter field must" + "be marked with @MultiCacheField in the corresponding model class. The resulting key" + - "must be a unique combination of the multiple keys, otherwise inconsistencies may occur.") - else -> null + "must be a unique combination of the multiple keys, otherwise inconsistencies may occur."); + } + } + return null; } - open fun getCachingId(model: T): Any? = - getCachingId(getCachingColumnValuesFromModel(arrayOfNulls(cachingColumnSize), model)) - + public Object getCachingId(T model){ + return getCachingId(getCachingColumnValuesFromModel(new Object[cachingColumnSize], model)); + } /** * Reloads relationships when loading from [FlowCursor] in a model that's cacheable. By having @@ -86,6 +107,6 @@ abstract class CacheAdapter(val modelCache: ModelCache, * * @param cursor The cursor to reload from. */ - open fun reloadRelationships(model: T, cursor: FlowCursor, databaseWrapper: DatabaseWrapper) = Unit + public abstract void reloadRelationships(T model, FlowCursor cursor, DatabaseWrapper databaseWrapper); } \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/adapter/CreationAdapter.java b/lib/src/main/java/com/dbflow5/adapter/CreationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..4b6ab8a316a1cd351a6eecbb9d8572965ad6a158 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/CreationAdapter.java @@ -0,0 +1,50 @@ +package com.dbflow5.adapter; + +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; + +/** + * Description: Provides a set of methods for creating a DB object. + */ +public interface CreationAdapter { + /** + * @return The query used to create this table. + */ + String getCreationQuery(); + + String getName(); + + ObjectType getType(); + + /** + * @return When false, this table gets generated and associated with database, however it will not immediately + * get created upon startup. This is useful for keeping around legacy tables for migrations. + */ + default boolean createWithDatabase() { + return true; + } + + /** + * Runs the creation query on the DB. + */ + default void createIfNotExists(DatabaseWrapper wrapper) { + DatabaseStatement statement = wrapper.compileStatement(getCreationQuery()); + statement.execute(); + } + + /** + * Drops the table by running a drop query. + */ + default void drop(DatabaseWrapper wrapper, boolean ifExists) { + String exist = ""; + if (ifExists) { + exist = "IF EXISTS"; + } + String sql = "DROP " + getType().value + " " + getName() + " " + exist + ";"; + DatabaseStatement statement = wrapper.compileStatement(sql); + statement.execute(); + } + +} + + diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/InternalAdapter.kt b/lib/src/main/java/com/dbflow5/adapter/InternalAdapter.java similarity index 67% rename from lib/src/main/kotlin/com/dbflow5/adapter/InternalAdapter.kt rename to lib/src/main/java/com/dbflow5/adapter/InternalAdapter.java index 761735d95b255f2afb6963c2de9d27033d38cbed..7ab8311d797881a174e8fe2d3fa85d25d9c0b485 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/InternalAdapter.kt +++ b/lib/src/main/java/com/dbflow5/adapter/InternalAdapter.java @@ -1,27 +1,30 @@ -package com.dbflow5.adapter +package com.dbflow5.adapter; -import android.content.ContentValues -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.DatabaseWrapper +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import ohos.data.rdb.ValuesBucket; + +import java.util.Collection; /** * Description: Used for our internal Adapter classes such as generated [ModelAdapter]. */ -interface InternalAdapter { +public interface InternalAdapter { /** * @return The table name of this adapter. */ - val name: String + String getName(); /** * Saves the specified model to the DB. * * @param model The model to save/insert/update * @param databaseWrapper The manually specified wrapper. + * @return is success? */ - fun save(model: TModel, databaseWrapper: DatabaseWrapper): Boolean + boolean save(TModel model, DatabaseWrapper databaseWrapper); /** * Saves a [Collection] of models to the DB. @@ -30,15 +33,16 @@ interface InternalAdapter { * @param databaseWrapper The manually specified wrapper * @return the count of models saved or updated. */ - fun saveAll(models: Collection, databaseWrapper: DatabaseWrapper): Long + long saveAll(Collection models, DatabaseWrapper databaseWrapper); /** * Inserts the specified model into the DB. * * @param model The model to insert. * @param databaseWrapper The manually specified wrapper. + * @return is success? */ - fun insert(model: TModel, databaseWrapper: DatabaseWrapper): Long + long insert(TModel model, DatabaseWrapper databaseWrapper); /** * Inserts a [Collection] of models into the DB. @@ -47,15 +51,16 @@ interface InternalAdapter { * @param databaseWrapper The manually specified wrapper * @return the count inserted */ - fun insertAll(models: Collection, databaseWrapper: DatabaseWrapper): Long + long insertAll(Collection models, DatabaseWrapper databaseWrapper); /** * Updates the specified model into the DB. * * @param model The model to update. * @param databaseWrapper The manually specified wrapper. + * @return is success? */ - fun update(model: TModel, databaseWrapper: DatabaseWrapper): Boolean + boolean update(TModel model, DatabaseWrapper databaseWrapper); /** * Updates a [Collection] of models in the DB. @@ -64,15 +69,16 @@ interface InternalAdapter { * @param databaseWrapper The manually specified wrapper * @return count of successful updates. */ - fun updateAll(models: Collection, databaseWrapper: DatabaseWrapper): Long + long updateAll(Collection models, DatabaseWrapper databaseWrapper); /** * Deletes the model from the DB * * @param model The model to delete * @param databaseWrapper The manually specified wrapper. + * @return is success? */ - fun delete(model: TModel, databaseWrapper: DatabaseWrapper): Boolean + boolean delete(TModel model, DatabaseWrapper databaseWrapper); /** * Updates a [Collection] of models in the DB. @@ -81,7 +87,7 @@ interface InternalAdapter { * @param databaseWrapper The manually specified wrapper * @return count of successful deletions. */ - fun deleteAll(models: Collection, databaseWrapper: DatabaseWrapper): Long + long deleteAll(Collection models, DatabaseWrapper databaseWrapper); /** * Binds to a [DatabaseStatement] for insert. @@ -89,7 +95,7 @@ interface InternalAdapter { * @param sqLiteStatement The statement to bind to. * @param model The model to read from. */ - fun bindToInsertStatement(sqLiteStatement: DatabaseStatement, model: TModel) + void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model); /** * Binds a [TModel] to the specified db statement @@ -97,7 +103,7 @@ interface InternalAdapter { * @param contentValues The content values to fill. * @param model The model values to put on the contentvalues */ - fun bindToContentValues(contentValues: ContentValues, model: TModel) + void bindToContentValues(ValuesBucket contentValues, TModel model); /** * Binds a [TModel] to the specified db statement, leaving out the [PrimaryKey.autoincrement] @@ -106,18 +112,18 @@ interface InternalAdapter { * @param contentValues The content values to fill. * @param model The model values to put on the content values. */ - fun bindToInsertValues(contentValues: ContentValues, model: TModel) + void bindToInsertValues(ValuesBucket contentValues, TModel model); /** * Binds values of the model to an update [DatabaseStatement]. It repeats each primary * key binding twice to ensure proper update statements. */ - fun bindToUpdateStatement(updateStatement: DatabaseStatement, model: TModel) + void bindToUpdateStatement(DatabaseStatement updateStatement, TModel model); /** * Binds values of the model to a delete [DatabaseStatement]. */ - fun bindToDeleteStatement(deleteStatement: DatabaseStatement, model: TModel) + void bindToDeleteStatement(DatabaseStatement deleteStatement, TModel model); /** * If a model has an autoincrementing primary key, then @@ -126,10 +132,10 @@ interface InternalAdapter { * @param model The model object to store the key * @param id The key to store */ - fun updateAutoIncrement(model: TModel, id: Number) + void updateAutoIncrement(TModel model, Number id); /** * @return true if the [InternalAdapter] can be cached. */ - fun cachingEnabled(): Boolean + boolean cachingEnabled(); } diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/ModelAdapter.kt b/lib/src/main/java/com/dbflow5/adapter/ModelAdapter.java similarity index 35% rename from lib/src/main/kotlin/com/dbflow5/adapter/ModelAdapter.kt rename to lib/src/main/java/com/dbflow5/adapter/ModelAdapter.java index f03c22d8d8c45fa53acfab14a36c5b472d01de39..eb57dc1a773c1dcf5cb8c880d4cc126d2b34e87a 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/ModelAdapter.kt +++ b/lib/src/main/java/com/dbflow5/adapter/ModelAdapter.java @@ -1,61 +1,67 @@ -package com.dbflow5.adapter - -import android.content.ContentValues -import com.dbflow5.adapter.saveable.ListModelSaver -import com.dbflow5.adapter.saveable.ModelSaver -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowLog -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.property.IProperty -import com.dbflow5.query.property.Property +package com.dbflow5.adapter; + +import com.dbflow5.adapter.saveable.ListModelSaver; +import com.dbflow5.adapter.saveable.ModelSaver; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowLog; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import ohos.data.rdb.ValuesBucket; + +import java.util.Collection; /** * Description: Used for generated classes from the combination of [Table] and [Model]. */ -abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) - : RetrievalAdapter(databaseDefinition), InternalAdapter, CreationAdapter { +public abstract class ModelAdapter extends RetrievalAdapter implements InternalAdapter, CreationAdapter { - private var _modelSaver: ModelSaver? = null + private ModelSaver _modelSaver; + ListModelSaver listModelSaver; - val listModelSaver: ListModelSaver by lazy { createListModelSaver() } + public ModelAdapter(DBFlowDatabase databaseDefinition){ + super(databaseDefinition); + listModelSaver = createListModelSaver(); + + if(tableConfig != null && tableConfig.modelSaver != null) { + ModelSaver modelSaver = tableConfig.modelSaver; + modelSaver.modelAdapter = this; + _modelSaver = modelSaver; + } + } /** * @return An array of column properties, in order of declaration. */ - abstract val allColumnProperties: Array> + public abstract IProperty[] getAllColumnProperties(); /** * @return The query used to insert a model using a [DatabaseStatement] */ - protected abstract val insertStatementQuery: String + protected abstract String getInsertStatementQuery(); - protected abstract val updateStatementQuery: String + protected abstract String getUpdateStatementQuery(); - protected abstract val deleteStatementQuery: String + protected abstract String getDeleteStatementQuery(); - protected abstract val saveStatementQuery: String + protected abstract String getSaveStatementQuery(); /** * @return The conflict algorithm to use when updating a row in this table. */ - open val updateOnConflictAction: ConflictAction - get() = ConflictAction.ABORT + public ConflictAction getUpdateOnConflictAction(){ + return ConflictAction.ABORT; + } /** * @return The conflict algorithm to use when inserting a row in this table. */ - open val insertOnConflictAction: ConflictAction - get() = ConflictAction.ABORT - - init { - tableConfig?.modelSaver?.let { modelSaver -> - modelSaver.modelAdapter = this - _modelSaver = modelSaver - } + public ConflictAction getInsertOnConflictAction() { + return ConflictAction.ABORT; } /** @@ -63,87 +69,101 @@ abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) * @return a new compiled [DatabaseStatement] representing insert. Not cached, always generated. * To bind values use [bindToInsertStatement]. */ - fun getInsertStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement = - databaseWrapper.compileStatement(insertStatementQuery) + public DatabaseStatement getInsertStatement(DatabaseWrapper databaseWrapper){ + return databaseWrapper.compileStatement(getInsertStatementQuery()); + } /** * @param databaseWrapper The database used to do an update statement. * @return a new compiled [DatabaseStatement] representing update. Not cached, always generated. * To bind values use [bindToUpdateStatement]. */ - fun getUpdateStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement = - databaseWrapper.compileStatement(updateStatementQuery) + public DatabaseStatement getUpdateStatement(DatabaseWrapper databaseWrapper){ + return databaseWrapper.compileStatement(getUpdateStatementQuery()); + } /** * @param databaseWrapper The database used to do a delete statement. * @return a new compiled [DatabaseStatement] representing delete. Not cached, always generated. * To bind values use [bindToDeleteStatement]. */ - fun getDeleteStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement = - databaseWrapper.compileStatement(deleteStatementQuery) + public DatabaseStatement getDeleteStatement(DatabaseWrapper databaseWrapper){ + return databaseWrapper.compileStatement(getDeleteStatementQuery()); + } /** * @param databaseWrapper The database used to do a save (insert or replace) statement. * @return a new compiled [DatabaseStatement] representing insert or replace. Not cached, always generated. * To bind values use [bindToInsertStatement]. */ - fun getSaveStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement = - databaseWrapper.compileStatement(saveStatementQuery) + public DatabaseStatement getSaveStatement(DatabaseWrapper databaseWrapper){ + return databaseWrapper.compileStatement(getSaveStatementQuery()); + } - override fun save(model: T, databaseWrapper: DatabaseWrapper): Boolean { - checkInTransaction(databaseWrapper) - return modelSaver.save(model, databaseWrapper) + @Override + public boolean save(T model, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return getModelSaver().save(model, databaseWrapper); } - override fun saveAll(models: Collection, databaseWrapper: DatabaseWrapper): Long { - checkInTransaction(databaseWrapper) - return listModelSaver.saveAll(models, databaseWrapper) + @Override + public long saveAll(Collection models, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return listModelSaver.saveAll(models, databaseWrapper); } - override fun insert(model: T, databaseWrapper: DatabaseWrapper): Long { - checkInTransaction(databaseWrapper) - return modelSaver.insert(model, databaseWrapper) + @Override + public long insert(T model, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return getModelSaver().insert(model, databaseWrapper); } - override fun insertAll(models: Collection, databaseWrapper: DatabaseWrapper): Long { - checkInTransaction(databaseWrapper) - return listModelSaver.insertAll(models, databaseWrapper) + @Override + public long insertAll(Collection models, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return listModelSaver.insertAll(models, databaseWrapper); } - override fun update(model: T, databaseWrapper: DatabaseWrapper): Boolean { - checkInTransaction(databaseWrapper) - return modelSaver.update(model, databaseWrapper) + @Override + public boolean update(T model, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return getModelSaver().update(model, databaseWrapper); } - override fun updateAll(models: Collection, databaseWrapper: DatabaseWrapper): Long { - checkInTransaction(databaseWrapper) - return listModelSaver.updateAll(models, databaseWrapper) + @Override + public long updateAll(Collection models, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return listModelSaver.updateAll(models, databaseWrapper); } - override fun delete(model: T, databaseWrapper: DatabaseWrapper): Boolean { - checkInTransaction(databaseWrapper) - return modelSaver.delete(model, databaseWrapper) + @Override + public boolean delete(T model, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return getModelSaver().delete(model, databaseWrapper); } - override fun deleteAll(models: Collection, databaseWrapper: DatabaseWrapper): Long { - checkInTransaction(databaseWrapper) - return listModelSaver.deleteAll(models, databaseWrapper) + @Override + public long deleteAll(Collection models, DatabaseWrapper databaseWrapper) { + checkInTransaction(databaseWrapper); + return listModelSaver.deleteAll(models, databaseWrapper); } - private fun checkInTransaction(databaseWrapper: DatabaseWrapper) { - if (!databaseWrapper.isInTransaction) { + private void checkInTransaction(DatabaseWrapper databaseWrapper) { + if (!databaseWrapper.isInTransaction()) { FlowLog.log(FlowLog.Level.W, "Database Not Running in a Transaction. Performance may be impacted, observability " + - "will need manual updates via db.tableObserver.checkForTableUpdates()") + "will need manual updates via db.tableObserver.checkForTableUpdates()"); } } - override fun bindToContentValues(contentValues: ContentValues, model: T) { - bindToInsertValues(contentValues, model) + @Override + public void bindToContentValues(ValuesBucket contentValues, T model) { + bindToInsertValues(contentValues, model); } - override fun bindToInsertValues(contentValues: ContentValues, model: T) { - throw RuntimeException("ContentValues are no longer generated automatically. To enable it," + - " set generateContentValues = true in @Table for $table.") + @Override + public void bindToInsertValues(ValuesBucket contentValues, T model) { + throw new RuntimeException("ContentValues are no longer generated automatically. To enable it," + + " set generateContentValues = true in @Table for $table."); } /** @@ -153,7 +173,8 @@ abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) * @param model The model object to store the key * @param id The key to store */ - override fun updateAutoIncrement(model: T, id: Number) { + @Override + public void updateAutoIncrement(T model, Number id) { } @@ -161,7 +182,7 @@ abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) * Called when we want to save our [ForeignKey] objects. usually during insert + update. * This method is overridden when [ForeignKey] specified */ - open fun saveForeignKeys(model: T, wrapper: DatabaseWrapper) { + public void saveForeignKeys(T model, DatabaseWrapper wrapper) { } @@ -169,25 +190,38 @@ abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) * Called when we want to delete our [ForeignKey] objects. During deletion [.delete] * This method is overridden when [ForeignKey] specified */ - open fun deleteForeignKeys(model: T, wrapper: DatabaseWrapper) { + public void deleteForeignKeys(T model, DatabaseWrapper wrapper) { } - override fun cachingEnabled(): Boolean = false + @Override + public boolean cachingEnabled() { + return false; + } - var modelSaver: ModelSaver - get() = _modelSaver - ?: createSingleModelSaver() - .apply { modelAdapter = this@ModelAdapter } - .also { _modelSaver = it } - set(value) { - this._modelSaver = value - value.modelAdapter = this + public void setModelSaver(ModelSaver _modelSaver){ + this._modelSaver = _modelSaver; + _modelSaver.modelAdapter = this; + } + + public ModelSaver getModelSaver(){ + if(_modelSaver != null){ + return _modelSaver; + }else { + ModelSaver singleModelSaver = createSingleModelSaver(); + singleModelSaver.modelAdapter = this; + _modelSaver = singleModelSaver; + return singleModelSaver; } + } - protected open fun createSingleModelSaver(): ModelSaver = ModelSaver() + protected ModelSaver createSingleModelSaver(){ + return new ModelSaver(); + } - protected open fun createListModelSaver(): ListModelSaver = ListModelSaver(modelSaver) + protected ListModelSaver createListModelSaver(){ + return new ListModelSaver(getModelSaver()); + } /** * Retrieves a property by name from the table via the corresponding generated "_Table" class. Useful @@ -196,7 +230,5 @@ abstract class ModelAdapter(databaseDefinition: DBFlowDatabase) * @param columnName The column name of the property. * @return The property from the corresponding Table class. */ - abstract fun getProperty(columnName: String): Property<*> - - + public abstract Property getProperty(String columnName); } diff --git a/lib/src/main/java/com/dbflow5/adapter/ModelViewAdapter.java b/lib/src/main/java/com/dbflow5/adapter/ModelViewAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..e4fbbe1889c5a81d8fe8ea2de9f9d23e552e9c9c --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/ModelViewAdapter.java @@ -0,0 +1,13 @@ +package com.dbflow5.adapter; + +import com.dbflow5.config.DBFlowDatabase; + +/** + * Description: The base class for a [T] adapter that defines how it interacts with the DB. + */ +public abstract class ModelViewAdapter extends RetrievalAdapter implements CreationAdapter { + public ModelViewAdapter(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } +} + diff --git a/lib/src/main/java/com/dbflow5/adapter/ObjectType.java b/lib/src/main/java/com/dbflow5/adapter/ObjectType.java new file mode 100644 index 0000000000000000000000000000000000000000..fe203652b535a819e2a11c37f08120da6963f3e3 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/ObjectType.java @@ -0,0 +1,12 @@ +package com.dbflow5.adapter; + +public enum ObjectType { + View("VIEW"), + Table("TABLE"); + + String value; + + ObjectType(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/QueryModelAdapter.kt b/lib/src/main/java/com/dbflow5/adapter/QueryModelAdapter.java similarity index 32% rename from lib/src/main/kotlin/com/dbflow5/adapter/QueryModelAdapter.kt rename to lib/src/main/java/com/dbflow5/adapter/QueryModelAdapter.java index 323659e9439f707ecb3ac7b6e5cfac458fe2178a..75203f99e701c73b99a70774aa6109204c889b7b 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/QueryModelAdapter.kt +++ b/lib/src/main/java/com/dbflow5/adapter/QueryModelAdapter.java @@ -1,13 +1,14 @@ -package com.dbflow5.adapter +package com.dbflow5.adapter; -import com.dbflow5.annotation.QueryModel -import com.dbflow5.config.DBFlowDatabase +import com.dbflow5.config.DBFlowDatabase; /** * Description: The baseclass for adapters to [QueryModel] that defines how it interacts with the DB. The * where query is not defined here, rather its determined by the cursor used. */ -@Deprecated(replaceWith = ReplaceWith("RetrievalAdapter", "com.dbflow5.adapter"), - message = "QueryModelAdapter is now redundant. Use Retrieval Adapter") -abstract class QueryModelAdapter(databaseDefinition: DBFlowDatabase) - : RetrievalAdapter(databaseDefinition) +@Deprecated +public abstract class QueryModelAdapter extends RetrievalAdapter { + public QueryModelAdapter(DBFlowDatabase databaseDefinition) { + super(databaseDefinition); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/RetrievalAdapter.kt b/lib/src/main/java/com/dbflow5/adapter/RetrievalAdapter.java similarity index 40% rename from lib/src/main/kotlin/com/dbflow5/adapter/RetrievalAdapter.kt rename to lib/src/main/java/com/dbflow5/adapter/RetrievalAdapter.java index f0268bb264f6c8d53daf6eafb8e3b6f9bf2f7076..0425994ce6c222e33fb0ed54eb470b1cf7fe4cfc 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/RetrievalAdapter.kt +++ b/lib/src/main/java/com/dbflow5/adapter/RetrievalAdapter.java @@ -1,21 +1,26 @@ -package com.dbflow5.adapter - -import com.dbflow5.adapter.queriable.ListModelLoader -import com.dbflow5.adapter.queriable.SingleModelLoader -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowManager -import com.dbflow5.config.TableConfig -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.OperatorGroup -import com.dbflow5.query.select -import com.dbflow5.query.selectCountOf +package com.dbflow5.adapter; + +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseConfig; +import com.dbflow5.config.FlowManager; +import com.dbflow5.config.TableConfig; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.Select; /** * Description: Provides a base retrieval class for all [Model] backed * adapters. */ -abstract class RetrievalAdapter(databaseDefinition: DBFlowDatabase) { +public abstract class RetrievalAdapter { + + public RetrievalAdapter(DBFlowDatabase databaseDefinition){ + init(databaseDefinition); + } /** * Overrides the default implementation and allows you to provide your own implementation. Defines @@ -23,13 +28,20 @@ abstract class RetrievalAdapter(databaseDefinition: DBFlowDatabase) { * * @param singleModelLoader The loader to use. */ - private var _singleModelLoader: SingleModelLoader? = null + private SingleModelLoader _singleModelLoader = null; + + public void setSingleModelLoader(SingleModelLoader singleModelLoader){ + this._singleModelLoader = singleModelLoader; + } - var singleModelLoader: SingleModelLoader - get() = _singleModelLoader ?: createSingleModelLoader().also { _singleModelLoader = it } - set(value) { - this._singleModelLoader = value + public SingleModelLoader getSingleModelLoader(){ + if(_singleModelLoader == null){ + _singleModelLoader = createSingleModelLoader(); + return _singleModelLoader; } + return _singleModelLoader; + } + /** * @return A new [ListModelLoader], caching will override this loader instance. */ @@ -39,44 +51,49 @@ abstract class RetrievalAdapter(databaseDefinition: DBFlowDatabase) { * * @param listModelLoader The loader to use. */ - private var _listModelLoader: ListModelLoader? = null + private ListModelLoader _listModelLoader = null; + + public void setListModelLoader(ListModelLoader value){ + this._listModelLoader = value; + } - var listModelLoader: ListModelLoader - get() = _listModelLoader ?: createListModelLoader().also { _listModelLoader = it } - set(value) { - this._listModelLoader = value + public ListModelLoader getListModelLoader(){ + if(_listModelLoader == null){ + _listModelLoader = createListModelLoader(); } + return _listModelLoader; + } - protected var tableConfig: TableConfig? = null - private set + protected TableConfig tableConfig = null; /** * @return the model class this adapter corresponds to */ - abstract val table: Class + public abstract Class table(); /** * @return A new instance of a [SingleModelLoader]. Subsequent calls do not cache * this object so it's recommended only calling this in bulk if possible. */ - val nonCacheableSingleModelLoader: SingleModelLoader - get() = SingleModelLoader(table) + public SingleModelLoader getNonCacheableSingleModelLoader(){ + return new SingleModelLoader(table()); + } /** * @return A new instance of a [ListModelLoader]. Subsequent calls do not cache * this object so it's recommended only calling this in bulk if possible. */ - val nonCacheableListModelLoader: ListModelLoader - get() = ListModelLoader(table) + public ListModelLoader getNonCacheableListModelLoader(){ + return new ListModelLoader(table()); + } - init { - val databaseConfig = FlowManager.getConfig() - .getConfigForDatabase(databaseDefinition.associatedDatabaseClassFile) + private void init(DBFlowDatabase databaseDefinition) { + DatabaseConfig databaseConfig = FlowManager.getConfig().getConfigForDatabase(databaseDefinition.associatedDatabaseClassFile()); if (databaseConfig != null) { - tableConfig = databaseConfig.getTableConfigForTable(table) + tableConfig = databaseConfig.getTableConfigForTable(table()); if (tableConfig != null) { - tableConfig?.singleModelLoader?.let { _singleModelLoader = it } - tableConfig?.listModelLoader?.let { _listModelLoader = it } + _singleModelLoader = tableConfig.singleModelLoader; + _listModelLoader = tableConfig.listModelLoader; } } } @@ -84,11 +101,10 @@ abstract class RetrievalAdapter(databaseDefinition: DBFlowDatabase) { /** * Returns a new [model] based on the object passed in. Will not overwrite existing object. */ - open fun load(model: T, databaseWrapper: DatabaseWrapper): T? = - nonCacheableSingleModelLoader.load(databaseWrapper, - (select - from table.kotlin - where getPrimaryConditionClause(model)).query) + public T load(T model, DatabaseWrapper databaseWrapper){ + return getNonCacheableSingleModelLoader().load(databaseWrapper, + (Select.select().from(table()).where(getPrimaryConditionClause(model)).getQuery())); + } /** * Converts the specified [FlowCursor] into a new [T] @@ -96,31 +112,36 @@ abstract class RetrievalAdapter(databaseDefinition: DBFlowDatabase) { * @param cursor The cursor to load into the model * @param wrapper The database instance to use. */ - abstract fun loadFromCursor(cursor: FlowCursor, wrapper: DatabaseWrapper): T + public abstract T loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper); /** * @param model The model to query values from * @return True if it exists as a row in the corresponding database table */ - open fun exists(model: T, databaseWrapper: DatabaseWrapper): Boolean = selectCountOf() - .from(table) - .where(getPrimaryConditionClause(model)) - .hasData(databaseWrapper) + public boolean exists(T model, DatabaseWrapper databaseWrapper) { + return SQLite.selectCountOf() + .from(table()) + .where(getPrimaryConditionClause(model)) + .hasData(databaseWrapper); + } /** * @param model The primary condition clause. * @return The clause that contains necessary primary conditions for this table. */ - abstract fun getPrimaryConditionClause(model: T): OperatorGroup + public abstract OperatorGroup getPrimaryConditionClause(T model); /** * @return A new [ListModelLoader], caching will override this loader instance. */ - protected open fun createListModelLoader(): ListModelLoader = ListModelLoader(table) + protected ListModelLoader createListModelLoader(){ + return new ListModelLoader<>(table()); + } /** * @return A new [SingleModelLoader], caching will override this loader instance. */ - protected open fun createSingleModelLoader(): SingleModelLoader = SingleModelLoader(table) - + protected SingleModelLoader createSingleModelLoader(){ + return new SingleModelLoader<>(table()); + } } \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableListModelLoader.java b/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableListModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..79407effe1f7f957a8891dcf02759bcc43712a06 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableListModelLoader.java @@ -0,0 +1,55 @@ +package com.dbflow5.adapter.queriable; + +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.cache.ModelCache; + +import java.util.ArrayList; + +/** + * Description: Loads a [List] of [T] with [Table.cachingEnabled] true. + */ +public class CacheableListModelLoader extends ListModelLoader { + + protected CacheAdapter cacheAdapter; + protected ModelAdapter modelAdapter; + + protected ModelCache modelCache; + + public CacheableListModelLoader(Class modelClass, CacheAdapter cacheAdapter){ + super(modelClass); + this.cacheAdapter = cacheAdapter; + modelAdapter = getModelAdapter(); + modelCache = cacheAdapter.modelCache; + } + + protected ModelAdapter getModelAdapter() { + if(getInstanceAdapter() instanceof ModelAdapter){ + ModelAdapter modelAdapter = (ModelAdapter)getInstanceAdapter(); + if (!modelAdapter.cachingEnabled()) { + throw new IllegalArgumentException("You cannot call this method for a table that has" + + " no caching id. Either use one Primary Key or use the MultiCacheKeyConverter"); + } + return modelAdapter; + } + throw new IllegalArgumentException("A non-Table type was used."); + } + + @Override + public ArrayList convertToData(FlowCursor cursor, DatabaseWrapper databaseWrapper) { + ArrayList data = new ArrayList<>(); + Object[] cacheValues = new Object[cacheAdapter.cachingColumnSize]; + // Ensure that we aren't iterating over this cursor concurrently from different threads + if (cursor.goToFirstRow()) { + do { + Object[] values = cacheAdapter.getCachingColumnValuesFromCursor(cacheValues, cursor); + T model = ModelCache.addOrReload(modelCache, cacheAdapter.getCachingId(values), + cacheAdapter, modelAdapter, cursor, databaseWrapper); + data.add(model); + } while (cursor.goToNextRow()); + } + return data; + } +} diff --git a/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableModelLoader.java b/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..be11221820e1ae87192efe2e28e859420ed93aea --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/CacheableModelLoader.java @@ -0,0 +1,55 @@ +package com.dbflow5.adapter.queriable; + +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.cache.ModelCache; + +/** + * Description: Loads model data that is backed by a [ModelCache]. Used when [Table.cachingEnabled] + * is true. + */ +public class CacheableModelLoader extends SingleModelLoader{ + + protected CacheAdapter cacheAdapter; + protected ModelAdapter modelAdapter; + + protected ModelCache modelCache; + + public CacheableModelLoader(Class modelClass, CacheAdapter cacheAdapter){ + super(modelClass); + this.cacheAdapter = cacheAdapter; + modelAdapter = getModelAdapter(); + modelCache = cacheAdapter.modelCache; + } + + private ModelAdapter getModelAdapter(){ + if(getInstanceAdapter() instanceof ModelAdapter){ + ModelAdapter modelAdapter = (ModelAdapter)getInstanceAdapter(); + if (!modelAdapter.cachingEnabled()) { + throw new IllegalArgumentException(("You cannot call this method for a table that or has no caching id " + + "or use one Primary Key " + + "or use the MultiCacheKeyConverter").trim()); + } + return modelAdapter; + }else { + throw new IllegalArgumentException("A non-Table type was used."); + } + } + + /** + * Converts data by loading from cache based on its sequence of caching ids. Will reuse the passed + * [T] if it's not found in the cache and non-null. + * + * @return A model from cache. + */ + @Override + public T convertToData(FlowCursor cursor, boolean moveToFirst, DatabaseWrapper databaseWrapper) { + if (!moveToFirst || cursor.goToFirstRow()) { + Object[] values = cacheAdapter.getCachingColumnValuesFromCursor(new Object[cacheAdapter.cachingColumnSize], cursor); + return ModelCache.addOrReload(modelCache, cacheAdapter.getCachingId(values), cacheAdapter, modelAdapter, cursor, databaseWrapper); + } + return null; + } +} diff --git a/lib/src/main/java/com/dbflow5/adapter/queriable/ListModelLoader.java b/lib/src/main/java/com/dbflow5/adapter/queriable/ListModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..ef481d3fca4b8558b14169e5a0f5c594bbdd9473 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/ListModelLoader.java @@ -0,0 +1,27 @@ +package com.dbflow5.adapter.queriable; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; + +import java.util.ArrayList; + +/** + * Description: Loads a [List] of [T]. + */ +public class ListModelLoader extends ModelLoader>{ + + public ListModelLoader(Class modelClass){ + super(modelClass); + } + + @Override + public ArrayList convertToData(FlowCursor cursor, DatabaseWrapper databaseWrapper) { + ArrayList retData = new ArrayList<>(); + if (cursor.goToFirstRow()) { + do { + retData.add(getInstanceAdapter().loadFromCursor(cursor, databaseWrapper)); + } while (cursor.goToNextRow()); + } + return retData; + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/queriable/ModelLoader.kt b/lib/src/main/java/com/dbflow5/adapter/queriable/ModelLoader.java similarity index 36% rename from lib/src/main/kotlin/com/dbflow5/adapter/queriable/ModelLoader.kt rename to lib/src/main/java/com/dbflow5/adapter/queriable/ModelLoader.java index 0f66de4450ec5f2cce41b20dd70e6d0a6fd5c45d..6f33b141bff9e6ffc7fd836cfd9770656fe9034c 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/queriable/ModelLoader.kt +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/ModelLoader.java @@ -1,32 +1,42 @@ -package com.dbflow5.adapter.queriable +package com.dbflow5.adapter.queriable; -import com.dbflow5.adapter.RetrievalAdapter -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; /** * Description: Represents how models load from DB. It will query a [DatabaseWrapper] * and query for a [FlowCursor]. Then the cursor is used to convert itself into an object. */ -abstract class ModelLoader(val modelClass: Class) { +public abstract class ModelLoader{ + private Class modelClass; - protected val instanceAdapter: RetrievalAdapter by lazy { FlowManager.getRetrievalAdapter(modelClass) } + protected RetrievalAdapter instanceAdapter; - /** - * Loads the data from a query and returns it as a [TReturn]. + public ModelLoader(Class modelClass) { + this.modelClass = modelClass; + } + + public RetrievalAdapter getInstanceAdapter() { + if(instanceAdapter == null){ + instanceAdapter = FlowManager.getRetrievalAdapter(modelClass); + } + return instanceAdapter; + } + + /** Loads the data from a query and returns it as a [TReturn]. * * @param databaseWrapper A custom database wrapper object to use. * @param query The query to call. * @return The data loaded from the database. */ - open fun load(databaseWrapper: DatabaseWrapper, query: String): TReturn? = - load(databaseWrapper.rawQuery(query, null), databaseWrapper) + public TReturn load(DatabaseWrapper databaseWrapper, String query){ + return load(databaseWrapper.rawQuery(query, null), databaseWrapper); + } - open fun load(cursor: FlowCursor?, databaseWrapper: DatabaseWrapper): TReturn? { - var data: TReturn? = null - cursor?.use { data = convertToData(it, databaseWrapper) } - return data + public TReturn load(FlowCursor cursor, DatabaseWrapper databaseWrapper){ + return (TReturn) convertToData(cursor, databaseWrapper); } /** @@ -35,5 +45,5 @@ abstract class ModelLoader(val modelClass: Clas * @param cursor The cursor resulting from a query passed into [.load] * @return A new (or reused) instance that represents the [FlowCursor]. */ - abstract fun convertToData(cursor: FlowCursor, databaseWrapper: DatabaseWrapper): TReturn? + public abstract TReturn convertToData(FlowCursor cursor, DatabaseWrapper databaseWrapper); } diff --git a/lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableListModelLoader.java b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableListModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..4a4c77bc993fb32e2292c63c06ebb66f66455d46 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableListModelLoader.java @@ -0,0 +1,33 @@ +package com.dbflow5.adapter.queriable; + +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.cache.ModelCache; + +import java.util.ArrayList; + +/** + * Description: + */ +public class SingleKeyCacheableListModelLoader extends CacheableListModelLoader{ + + public SingleKeyCacheableListModelLoader(Class tModelClass, CacheAdapter cacheAdapter){ + super(tModelClass, cacheAdapter); + } + + @Override + public ArrayList convertToData(FlowCursor cursor, DatabaseWrapper databaseWrapper) { + ArrayList data = new ArrayList<>(); + Object cacheValue; + // Ensure that we aren't iterating over this cursor concurrently from different threads + if (cursor.goToFirstRow()) { + do { + cacheValue = cacheAdapter.getCachingColumnValueFromCursor(cursor); + T model = ModelCache.addOrReload(modelCache, cacheValue, cacheAdapter, modelAdapter, cursor, databaseWrapper); + data.add(model); + } while (cursor.goToFirstRow()); + } + return data; + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.kt b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.java similarity index 31% rename from lib/src/main/kotlin/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.kt rename to lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.java index c4c45fe02cd0d589e2c3f727f878884b0810c5f2..f820f6ae2f3f825a6a8484cdc989f3ea2e79dc0d 100644 --- a/lib/src/main/kotlin/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.kt +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleKeyCacheableModelLoader.java @@ -1,18 +1,19 @@ -package com.dbflow5.adapter.queriable +package com.dbflow5.adapter.queriable; -import com.dbflow5.adapter.CacheAdapter -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.cache.addOrReload -import com.dbflow5.structure.Model +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.cache.ModelCache; /** * Description: More optimized version of [CacheableModelLoader] which assumes that the [Model] * only utilizes a single primary key. */ -class SingleKeyCacheableModelLoader(modelClass: Class, - cacheAdapter: CacheAdapter) - : CacheableModelLoader(modelClass, cacheAdapter) { +public class SingleKeyCacheableModelLoader extends CacheableModelLoader{ + + public SingleKeyCacheableModelLoader(Class modelClass, CacheAdapter cacheAdapter){ + super(modelClass, cacheAdapter); + } /** * Converts data by loading from cache based on its sequence of caching ids. Will reuse the passed @@ -20,10 +21,12 @@ class SingleKeyCacheableModelLoader(modelClass: Class, * * @return A model from cache. */ - override fun convertToData(cursor: FlowCursor, moveToFirst: Boolean, databaseWrapper: DatabaseWrapper): T? { - return if (!moveToFirst || cursor.moveToFirst()) { - val value = cacheAdapter.getCachingColumnValueFromCursor(cursor) - modelCache.addOrReload(value, cacheAdapter, modelAdapter, cursor, databaseWrapper) - } else null + @Override + public T convertToData(FlowCursor cursor, boolean moveToFirst, DatabaseWrapper databaseWrapper) { + if (!moveToFirst || cursor.goToFirstRow()) { + Object value = cacheAdapter.getCachingColumnValueFromCursor(cursor); + return ModelCache.addOrReload(modelCache, value, cacheAdapter, modelAdapter, cursor, databaseWrapper); + } + return null; } } diff --git a/lib/src/main/java/com/dbflow5/adapter/queriable/SingleModelLoader.java b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..0042a7d54388a98d716631746162ca2fde3c3cc8 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/queriable/SingleModelLoader.java @@ -0,0 +1,27 @@ +package com.dbflow5.adapter.queriable; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; + +/** + * Description: Responsible for loading data into a single object. + */ +public class SingleModelLoader extends ModelLoader{ + + public SingleModelLoader(Class modelClass){ + super(modelClass); + } + + @Override + public T convertToData(FlowCursor cursor, DatabaseWrapper databaseWrapper) { + return convertToData(cursor, true, databaseWrapper); + } + + public T convertToData(FlowCursor cursor, boolean moveToFirst, DatabaseWrapper databaseWrapper){ + if (!moveToFirst || cursor.goToFirstRow()) { + return getInstanceAdapter().loadFromCursor(cursor, databaseWrapper); + } else { + return null; + } + } +} diff --git a/lib/src/main/java/com/dbflow5/adapter/saveable/CacheableListModelSaver.java b/lib/src/main/java/com/dbflow5/adapter/saveable/CacheableListModelSaver.java new file mode 100644 index 0000000000000000000000000000000000000000..c333001eaf7bd249c172eecc7b67c4eef92aad29 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/saveable/CacheableListModelSaver.java @@ -0,0 +1,79 @@ +package com.dbflow5.adapter.saveable; + +import com.dbflow5.adapter.CacheAdapter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Description: Used for model caching, enables caching models when saving in list. + */ +public class CacheableListModelSaver extends ListModelSaver { + private final CacheAdapter cacheAdapter; + + public CacheableListModelSaver(ModelSaver modelSaver, CacheAdapter cacheAdapter){ + super(modelSaver); + this.cacheAdapter = cacheAdapter; + } + + @Override + public synchronized long saveAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, + modelAdapter.getSaveStatement(wrapper), model -> { + cacheAdapter.storeModelInCache(model); + return null; + }, (model, databaseStatement) -> modelSaver.save(model, databaseStatement, wrapper)); + + } + + @Override + public synchronized long insertAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, + modelAdapter.getInsertStatement(wrapper), model -> { + cacheAdapter.storeModelInCache(model); + return null; + }, (model, databaseStatement) -> modelSaver.insert(model, databaseStatement, wrapper) > 0); + } + + @Override + public synchronized long updateAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, + modelAdapter.getUpdateStatement(wrapper), model -> { + cacheAdapter.storeModelInCache(model); + return null; + }, (model, databaseStatement) -> modelSaver.update(model, databaseStatement, wrapper)); + } + + @Override + public synchronized long deleteAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, + modelAdapter.getDeleteStatement(wrapper), model -> { + cacheAdapter.removeModelFromCache(model); + return null; + }, (model, databaseStatement) -> modelSaver.delete(model, databaseStatement, wrapper)); + } + + private long applyAndCount(Collection tableCollection, + DatabaseStatement databaseStatement, + Function cacheFn, + BiFunction fn) { + // skip if empty. + if (tableCollection.isEmpty()) { + return 0L; + } + + AtomicLong count = new AtomicLong(0L); + + tableCollection.forEach(model -> { + if (fn.apply(model, databaseStatement)) { + cacheFn.apply(model); + count.getAndIncrement(); + } + }); + return count.get(); + } +} diff --git a/lib/src/main/java/com/dbflow5/adapter/saveable/ListModelSaver.java b/lib/src/main/java/com/dbflow5/adapter/saveable/ListModelSaver.java new file mode 100644 index 0000000000000000000000000000000000000000..76fde11f235010d9b2f365b811a066b1eb6a143f --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/saveable/ListModelSaver.java @@ -0,0 +1,54 @@ +package com.dbflow5.adapter.saveable; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; + +import java.util.Collection; +import java.util.Iterator; +import java.util.function.BiFunction; + +public class ListModelSaver { + + ModelSaver modelSaver; + + ModelAdapter modelAdapter; + + public ListModelSaver(ModelSaver modelSaver){ + this.modelSaver = modelSaver; + modelAdapter = modelSaver.modelAdapter; + } + + + public synchronized long saveAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, modelAdapter.getSaveStatement(wrapper), (model, statement) -> modelSaver.save(model, statement, wrapper)); + } + + public synchronized long insertAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, modelAdapter.getInsertStatement(wrapper), (model, statement) -> modelSaver.insert(model, statement, wrapper) > ModelSaver.INSERT_FAILED); + } + + public synchronized long updateAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, modelAdapter.getUpdateStatement(wrapper), (model, statement) -> modelSaver.update(model, statement, wrapper)); + } + + public synchronized long deleteAll(Collection tableCollection, DatabaseWrapper wrapper) { + return applyAndCount(tableCollection, modelAdapter.getDeleteStatement(wrapper), (model, statement) -> modelSaver.delete(model, statement, wrapper)); + } + + private long applyAndCount(Collection tableCollection, DatabaseStatement databaseStatement, BiFunction fn) { + // skip if empty. + if (tableCollection.isEmpty()) { + return 0L; + } + + long count = 0L; + for (T t : tableCollection) { + if (fn.apply(t, databaseStatement)) { + count++; + } + } + + return count; + } +} diff --git a/lib/src/main/java/com/dbflow5/adapter/saveable/ModelSaver.java b/lib/src/main/java/com/dbflow5/adapter/saveable/ModelSaver.java new file mode 100644 index 0000000000000000000000000000000000000000..cc013d527400b609ee352f9841cd1c587ab6df02 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/adapter/saveable/ModelSaver.java @@ -0,0 +1,83 @@ +package com.dbflow5.adapter.saveable; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.runtime.NotifyDistributor; +import com.dbflow5.structure.ChangeAction; + +/** + * Description: Defines how models get saved into the DB. It will bind values to [DatabaseStatement] + * for all CRUD operations as they are wildly faster and more efficient than ContentValues. + */ +public class ModelSaver { + public static final int INSERT_FAILED = -1; + + public ModelAdapter modelAdapter; + + public synchronized boolean save(T model, DatabaseWrapper wrapper) { + DatabaseStatement insertStatement = modelAdapter.getSaveStatement(wrapper); + return save(model, insertStatement, wrapper); + } + + public synchronized boolean save(T model, DatabaseStatement insertStatement, DatabaseWrapper wrapper) { + modelAdapter.saveForeignKeys(model, wrapper); + modelAdapter.bindToInsertStatement(insertStatement, model); + long id = insertStatement.executeInsert(); + boolean success = id > INSERT_FAILED; + if (success) { + modelAdapter.updateAutoIncrement(model, id); + new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.CHANGE); + } + return success; + } + + public synchronized boolean update(T model, DatabaseWrapper wrapper) { + DatabaseStatement updateStatement = modelAdapter.getUpdateStatement(wrapper); + return update(model, updateStatement, wrapper); + } + + public synchronized boolean update(T model, DatabaseStatement databaseStatement, DatabaseWrapper wrapper) { + modelAdapter.saveForeignKeys(model, wrapper); + modelAdapter.bindToUpdateStatement(databaseStatement, model); + boolean successful = databaseStatement.executeUpdateDelete() != 0L; + if (successful) { + new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.UPDATE); + } + return successful; + } + + public synchronized long insert(T model, DatabaseWrapper wrapper) { + DatabaseStatement insertStatement = modelAdapter.getInsertStatement(wrapper); + return insert(model, insertStatement, wrapper); + } + + public synchronized long insert(T model, DatabaseStatement insertStatement, DatabaseWrapper wrapper) { + modelAdapter.saveForeignKeys(model, wrapper); + modelAdapter.bindToInsertStatement(insertStatement, model); + long id = insertStatement.executeInsert(); + if (id > INSERT_FAILED) { + modelAdapter.updateAutoIncrement(model, id); + new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.INSERT); + } + return id; + } + + public synchronized boolean delete(T model, DatabaseWrapper wrapper) { + DatabaseStatement deleteStatement = modelAdapter.getDeleteStatement(wrapper); + return delete(model, deleteStatement, wrapper); + } + + public synchronized boolean delete(T model, DatabaseStatement deleteStatement, DatabaseWrapper wrapper) { + modelAdapter.deleteForeignKeys(model, wrapper); + modelAdapter.bindToDeleteStatement(deleteStatement, model); + + boolean success = deleteStatement.executeUpdateDelete() != 0L; + if (success) { + new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.DELETE); + } + modelAdapter.updateAutoIncrement(model, 0); + return success; + } +} + diff --git a/lib/src/main/java/com/dbflow5/config/DBFlowDatabase.java b/lib/src/main/java/com/dbflow5/config/DBFlowDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..f3e624506062a8cb8f8dd7ffdb6ae911fbbec1b6 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/config/DBFlowDatabase.java @@ -0,0 +1,593 @@ +package com.dbflow5.config; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ModelViewAdapter; +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.ModelSaver; +import com.dbflow5.database.*; +import com.dbflow5.migration.Migration; +import com.dbflow5.observing.TableObserver; +import com.dbflow5.runtime.DirectModelNotifier; +import com.dbflow5.runtime.ModelNotifier; +import com.dbflow5.transaction.BaseTransactionManager; +import com.dbflow5.transaction.DefaultTransactionManager; +import com.dbflow5.transaction.ITransaction; +import com.dbflow5.transaction.Transaction; +import ohos.app.Context; + +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +import com.dbflow5.annotation.WorkerThread; + +/** + * Description: The main interface that all Database implementations extend from. Use this to + * pass in for operations and [Transaction]. + */ +public abstract class DBFlowDatabase implements DatabaseWrapper { + + public DBFlowDatabase(){ + init(); + } + + enum JournalMode { + Automatic, + Truncate, + + WriteAheadLogging; + + public JournalMode adjustIfAutomatic(Context context) { + if(this == Automatic){ + return this; + }else { + return WriteAheadLogging; + } +// +// Automatic -> this +// else -> { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { +// // check if low ram device +// val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && manager?.isLowRamDevice == false) { +// WriteAheadLogging +// } +// } +// Truncate +// } + } + } + + private final Map> migrationMap = new HashMap<>(); + + private final Map, ModelAdapter> modelAdapterMap = new HashMap<>(); + + private final Map> modelTableNames = new HashMap<>(); + + private final Map, ModelViewAdapter> modelViewAdapterMap = new LinkedHashMap<>(); + + private final Map, RetrievalAdapter> queryModelAdapterMap = new LinkedHashMap<>(); + + /** + * The helper that manages database changes and initialization + */ + private OpenHelper _openHelper = null; + + /** + * Allows for the app to listen for database changes. + */ + private DatabaseCallback callback = null; + + /** + * Used when resetting the DB + */ + private boolean isResetting = false; + + public BaseTransactionManager transactionManager; + + DatabaseConfig databaseConfig = null; + + private ModelNotifier modelNotifier = null; + + /** + * @return a list of all model classes in this database. + */ + public List> modelClasses() { + return new ArrayList<>(modelAdapterMap.keySet()); + } + + /** + * @return the [BaseModelView] list for this database. + */ + public List> modelViews(){ + return new ArrayList<>(modelViewAdapterMap.keySet()); + } + + /** + * @return The list of [ModelViewAdapter]. Internal method for + * creating model views in the DB. + */ + public List> modelViewAdapters(){ + return new ArrayList<>(modelViewAdapterMap.values()); + } + + /** + * @return The list of [RetrievalAdapter]. Internal method for creating query models in the DB. + */ + public List> queryModelAdapters(){ + return new ArrayList<>(queryModelAdapterMap.values()); + } + + /** + * @return The map of migrations to DB version + */ + public Map> getMigrations(){ + return migrationMap; + } + + + /** + * Returns true if the [openHelper] has been created. + */ + private boolean isOpened = false; + + public boolean isOpened(){ + return isOpened; + } + + private Lock closeLock; + + public Lock getCloseLock(){ + if(closeLock == null){ + closeLock = new ReentrantLock(); + } + return closeLock; + } + + private boolean writeAheadLoggingEnabled = false; + + //private OpenHelper openHelper; + public OpenHelper openHelper(){ + OpenHelper helper = _openHelper; + if (helper == null) { + DatabaseConfig config = FlowManager.getConfig().databaseConfigMap.get(associatedDatabaseClassFile()); + if (config != null && config.openHelperCreator != null) { + helper = config.openHelperCreator.createHelper(this, internalCallback); + } else { + helper = new OhosSQLiteOpenHelper(FlowManager.getContext(), this, internalCallback); + } + onOpenWithConfig(config, helper); + } + _openHelper = helper; + return helper; + } + + private void onOpenWithConfig(DatabaseConfig config, OpenHelper helper) { + helper.performRestoreFromBackup(); + + boolean wal = config != null && config.journalMode.adjustIfAutomatic(FlowManager.getContext()) == JournalMode.WriteAheadLogging; + helper.setWriteAheadLoggingEnabled(wal); + writeAheadLoggingEnabled = wal; + isOpened = true; + } + + public DatabaseWrapper getWritableDatabase(){ + return openHelper().database(); + } + + /** + * @return The name of this database as defined in [Database] + */ + public String getDatabaseName(){ + String databaseName; + if(databaseConfig == null || databaseConfig.databaseName == null){ + databaseName = associatedDatabaseClassFile().getSimpleName(); + }else { + databaseName = databaseConfig.databaseName; + } + return databaseName; + } + + /** + * @return The file name that this database points to + */ + public String getDatabaseFileName(){ + return getDatabaseName() + getDatabaseExtensionName(); + } + + /** + * @return the extension for the file name. + */ + public String getDatabaseExtensionName(){ + if(databaseConfig != null && databaseConfig.databaseExtensionName != null){ + return databaseConfig.databaseExtensionName; + } + return ".db"; + } + + /** + * @return True if the database will reside in memory. + */ + public boolean isInMemory(){ + return databaseConfig.isInMemory; + } + + /** + * @return The version of the database currently. + */ + public abstract int databaseVersion(); + + /** + * @return True if the [Database.foreignKeyConstraintsEnforced] annotation is true. + */ + public abstract boolean isForeignKeysSupported(); + + /** + * @return The class that defines the [Database] annotation. + */ + public abstract Class associatedDatabaseClassFile(); + + /** + * @return True if the database is ok. If backups are enabled, we restore from backup and will + * override the return value if it replaces the main DB. + */ + public boolean isDatabaseIntegrityOk(){ + return openHelper().isDatabaseIntegrityOk(); + } + + /** + * Returns the associated table observer that tracks changes to tables during transactions on + * the DB. + */ + public TableObserver tableObserver(){ + // observe all tables + List> classList = modelClasses(); + classList.addAll(modelViews()); + return new TableObserver(this, classList); + } + + private void init() { + applyDatabaseConfig(FlowManager.getConfig().databaseConfigMap.get(associatedDatabaseClassFile())); + } + + /** + * Applies a database configuration object to this class. + */ + private void applyDatabaseConfig(DatabaseConfig databaseConfig) { + this.databaseConfig = databaseConfig; + if (databaseConfig != null) { + // initialize configuration if exists. + Collection> tableConfigCollection = databaseConfig.tableConfigMap.values(); + for (TableConfig tableConfig : tableConfigCollection) { + ModelAdapter modelAdapter = (ModelAdapter) modelAdapterMap.get(tableConfig.tableClass); + if(modelAdapter == null){ + continue; + } + + if(tableConfig.listModelLoader != null) { + modelAdapter.setListModelLoader((ListModelLoader)tableConfig.listModelLoader); + } + if(tableConfig.singleModelLoader != null) { + modelAdapter.setSingleModelLoader((SingleModelLoader)tableConfig.singleModelLoader); + } + if(tableConfig.modelSaver != null) { + modelAdapter.setModelSaver((ModelSaver)tableConfig.modelSaver); + } + } + callback = databaseConfig.callback; + } + + if (databaseConfig == null || databaseConfig.transactionManagerCreator == null) { + transactionManager = new DefaultTransactionManager(this); + } else { + transactionManager = databaseConfig.transactionManagerCreator.createManager(this); + } + } + + protected void addModelAdapter(ModelAdapter modelAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(modelAdapter.table(), this); + modelTableNames.put(modelAdapter.getName(), modelAdapter.table()); + modelAdapterMap.put(modelAdapter.table(), modelAdapter); + } + + protected void addModelViewAdapter(ModelViewAdapter modelViewAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(modelViewAdapter.table(), this); + modelViewAdapterMap.put(modelViewAdapter.table(), modelViewAdapter); + } + + protected void addRetrievalAdapter(RetrievalAdapter retrievalAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(retrievalAdapter.table(), this); + queryModelAdapterMap.put(retrievalAdapter.table(), retrievalAdapter); + } + + protected void addMigration(int version, Migration migration) { + List list = migrationMap.getOrDefault(version, new ArrayList<>()); + list.add(migration); + } + + /** + * Internal method used to create the database schema. + * + * @return List of Model Adapters + */ + public List> modelAdapters(){ + return new ArrayList<>(modelAdapterMap.values()); + } + + /** + * Returns the associated [ModelAdapter] within this database for + * the specified table. If the Model is missing the [Table] annotation, + * this will return null. + * + * @param table The model that exists in this database. + * @return The ModelAdapter for the table. + */ + ModelAdapter getModelAdapterForTable(Class table) { + return (ModelAdapter)modelAdapterMap.get(table); + } + + /** + * @param tableName The name of the table in this db. + * @return The associated [ModelAdapter] within this database for the specified table name. + * If the Model is missing the [Table] annotation, this will return null. + */ + Class getModelClassForName(String tableName){ + return modelTableNames.get(tableName); + } + + /** + * @param table the VIEW class to retrieve the ModelViewAdapter from. + * @return the associated [ModelViewAdapter] for the specified table. + */ + ModelViewAdapter getModelViewAdapterForTable(Class table){ + return (ModelViewAdapter)modelViewAdapterMap.get(table); + } + + /** + * @param queryModel The [QueryModel] class + * @return The adapter that corresponds to the specified class. + */ + RetrievalAdapter getQueryModelAdapterForQueryClass(Class queryModel){ + return (RetrievalAdapter)queryModelAdapterMap.get(queryModel); + } + + ModelNotifier getModelNotifier() { + ModelNotifier notifier = modelNotifier; + if (notifier == null) { + DatabaseConfig config = FlowManager.getConfig().databaseConfigMap.get(associatedDatabaseClassFile()); + + if (config.modelNotifier == null) { + notifier = DirectModelNotifier.get(); + } else { + notifier = config.modelNotifier; + } + } + modelNotifier = notifier; + return notifier; + } + + public Transaction.Builder beginTransactionAsync(ITransaction transaction){ + return new Transaction.Builder<>(transaction, this); + } + + public Transaction.Builder beginTransactionAsync(Function transaction){ + return beginTransactionAsync((ITransaction) databaseWrapper -> { + transaction.apply(databaseWrapper); + return null; + }); + } + + /** + * This should never get called on the main thread. Use [beginTransactionAsync] for an async-variant. + * Runs a transaction in the current thread. + */ + @WorkerThread + public R executeTransaction(ITransaction transaction) { + try { + beginTransaction(); + R result = transaction.execute(getWritableDatabase()); + setTransactionSuccessful(); + return result; + } finally { + endTransaction(); + } + } + + /** + * This should never get called on the main thread. Use [beginTransactionAsync] for an async-variant. + * Runs a transaction in the current thread. + */ + @WorkerThread + public R executeTransactionFunc(Function transaction){ + return executeTransaction(transaction::apply); + } + + /** + * @return True if the [Database.consistencyCheckEnabled] annotation is true. + */ + public abstract boolean areConsistencyChecksEnabled(); + + /** + * @return True if the [Database.backupEnabled] annotation is true. + */ + public abstract boolean backupEnabled(); + + /** + * Performs a full deletion of this database. Reopens the [AndroidSQLiteOpenHelper] as well. + * + * Reapplies the [DatabaseConfig] if we have one. + * @param databaseConfig sets a new [DatabaseConfig] on this class. + */ + public void reset(DatabaseConfig databaseConfig) { + if(databaseConfig == null){ + databaseConfig = this.databaseConfig; + } + if (!isResetting) { + destroy(); + // reapply configuration before opening it. + applyDatabaseConfig(databaseConfig); + openHelper().database(); + } + } + + /** + * Reopens the DB with the new [DatabaseConfig] specified. + * Reapplies the [DatabaseConfig] if we have one. + * + * @param databaseConfig sets a new [DatabaseConfig] on this class. + */ + public void reopen(DatabaseConfig databaseConfig) { + if(databaseConfig == null){ + databaseConfig = this.databaseConfig; + } + if (!isResetting) { + close(); + _openHelper = null; + isOpened = false; + applyDatabaseConfig(databaseConfig); + openHelper().database(); + isResetting = false; + } + } + + /** + * Deletes the underlying database and destroys it. + */ + public void destroy() { + if (!isResetting) { + isResetting = true; + close(); + openHelper().deleteDB(); + _openHelper = null; + isOpened = false; + isResetting = false; + } + } + + /** + * Closes the DB and stops the [BaseTransactionManager] + */ + public void close() { + transactionManager.stopQueue(); + if (isOpened) { + try { + if(closeLock != null) { + closeLock.lock(); + } + openHelper().closeDB(); + isOpened = false; + } finally { + if(closeLock != null) { + closeLock.unlock(); + } + } + } + } + + /** + * Saves the database as a backup on the [DefaultTransactionQueue]. This will + * create a THIRD database to use as a backup to the backup in case somehow the overwrite fails. + * + * @throws java.lang.IllegalStateException if [Database.backupEnabled] + * or [Database.consistencyCheckEnabled] is not enabled. + */ + public void backupDatabase() { + openHelper().backupDB(); + } + + @Override + public int getVersion() { + return getWritableDatabase().getVersion(); + } + + @Override + public void execSQL(String query){ + getWritableDatabase().execSQL(query); + } + + @Override + public void beginTransaction() { + tableObserver().syncTriggers(getWritableDatabase()); + getWritableDatabase().beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + getWritableDatabase().setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + getWritableDatabase().endTransaction(); + if (!isInTransaction()) { + tableObserver().enqueueTableUpdateCheck(); + } + } + + @Override + public DatabaseStatement compileStatement(String rawQuery){ + return getWritableDatabase().compileStatement(rawQuery); + } + + @Override + public FlowCursor rawQuery(String query, String[] selectionArgs){ + return getWritableDatabase().rawQuery(query, selectionArgs); + } + + @Override + public int delete(String tableName, String[] whereClause, String[] whereArgs){ + return getWritableDatabase().delete(tableName, whereClause, whereArgs); + } + + @Override + public FlowCursor query(String tableName, String[] columns, String[] selectionColumns, String[] selectionArgs, + String groupBy, String having, String orderBy){ + return getWritableDatabase().query(tableName, columns, selectionColumns, selectionArgs, groupBy, having, orderBy); + } + + public boolean isInTransaction(){ + return getWritableDatabase().isInTransaction(); + } + + private final DatabaseCallback internalCallback = new DatabaseCallback() { + + @Override + public void onOpen(DatabaseWrapper database) { + tableObserver().construct(database); + if(callback != null) { + callback.onOpen(database); + } + } + + @Override + public void onCreate(DatabaseWrapper database) { + if(callback != null) { + callback.onCreate(database); + } + } + + @Override + public void onUpgrade(DatabaseWrapper database, int oldVersion, int newVersion) { + if(callback != null) { + callback.onUpgrade(database, oldVersion, newVersion); + } + } + + @Override + public void onDowngrade(DatabaseWrapper databaseWrapper, int oldVersion, int newVersion) { + if(callback != null) { + callback.onDowngrade(databaseWrapper, oldVersion, newVersion); + } + } + + @Override + public void onConfigure(DatabaseWrapper db) { + if(callback != null) { + callback.onConfigure(db); + } + } + }; +} diff --git a/lib/src/main/java/com/dbflow5/config/DatabaseConfig.java b/lib/src/main/java/com/dbflow5/config/DatabaseConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..b113acac51bf93944cd99ce9a64fb083965c379c --- /dev/null +++ b/lib/src/main/java/com/dbflow5/config/DatabaseConfig.java @@ -0,0 +1,208 @@ +package com.dbflow5.config; + +import com.dbflow5.StringUtils; +import com.dbflow5.database.DatabaseCallback; +import com.dbflow5.database.OpenHelper; +import com.dbflow5.runtime.ModelNotifier; +import com.dbflow5.transaction.BaseTransactionManager; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.regex.Pattern; + + +/** + * Description: + */ +public class DatabaseConfig { + public Class databaseClass; + public OpenHelperCreator openHelperCreator; + public TransactionManagerCreator transactionManagerCreator; + public DatabaseCallback callback; + public Map, TableConfig> tableConfigMap; + public ModelNotifier modelNotifier; + public boolean isInMemory; + public String databaseName; + public String databaseExtensionName; + public DBFlowDatabase.JournalMode journalMode; + + public DatabaseConfig(Class databaseClass, + OpenHelperCreator openHelperCreator, + TransactionManagerCreator transactionManagerCreator, + DatabaseCallback callback, + Map, TableConfig> tableConfigMap, + ModelNotifier modelNotifier, + boolean isInMemory, + String databaseName, + String databaseExtensionName, + DBFlowDatabase.JournalMode journalMode) { + this.databaseClass = databaseClass; + this.openHelperCreator = openHelperCreator; + this.transactionManagerCreator = transactionManagerCreator; + this.callback = callback; + this.tableConfigMap = tableConfigMap; + this.modelNotifier = modelNotifier; + this.isInMemory = isInMemory; + this.databaseName = databaseName; + this.databaseExtensionName = databaseExtensionName; + this.journalMode = journalMode; + } + + public DatabaseConfig(Builder builder) { + this.databaseClass = builder.databaseClass; + this.openHelperCreator = builder.openHelperCreator; + this.transactionManagerCreator = builder.transactionManagerCreator; + this.callback = builder.callback; + this.tableConfigMap = builder.tableConfigMap; + this.modelNotifier = builder.modelNotifier; + this.isInMemory = builder.inMemory; + this.databaseName = getDatabaseName(builder); + this.databaseExtensionName = getDatabaseExtensionName(builder); + this.journalMode = builder.journalMode; + if (!isValidDatabaseName(databaseName)) { + throw new IllegalArgumentException("Invalid database name " + databaseName + " found. Names must follow " + + "the [A-Za-z_]+[a-zA-Z0-9_] pattern."); + } + } + + private String getDatabaseName(Builder builder) { + String databaseName; + if (builder.databaseName != null) { + databaseName = builder.databaseName; + } else { + databaseName = builder.databaseClass.getSimpleName(); + } + return databaseName; + } + + private String getDatabaseExtensionName(Builder builder) { + String databaseExtensionName; + if (builder.databaseExtensionName == null) { + databaseExtensionName = ".db"; + } else if (StringUtils.isNotNullOrEmpty(builder.databaseExtensionName)) { + databaseExtensionName = "." + builder.databaseExtensionName; + } else { + databaseExtensionName = ""; + } + return databaseExtensionName; + } + + public TableConfig getTableConfigForTable(Class modelClass) { + return (TableConfig) tableConfigMap.get(modelClass); + } + + + public static Builder builder(Class database, OpenHelperCreator openHelperCreator) { + return new Builder(database, openHelperCreator); + } + + public static Builder inMemoryBuilder(Class database, OpenHelperCreator openHelperCreator) { + return new Builder(database, openHelperCreator).inMemory(); + } + + /** + * Checks if databaseName is valid. It will check if databaseName matches regex pattern + * [A-Za-z_$]+[a-zA-Z0-9_$] + * Examples: + * database - valid + * DbFlow1 - valid + * database.db - invalid (contains a dot) + * 1database - invalid (starts with a number) + * + * @param databaseName database name to validate. + * @return `true` if parameter is a valid database name, `false` otherwise. + */ + private static boolean isValidDatabaseName(String databaseName) { + Pattern javaClassNamePattern = Pattern.compile("[A-Za-z_$]+[a-zA-Z0-9_$]*"); + return javaClassNamePattern.matcher(databaseName).matches(); + } + + /** + * Build compatibility class for Java. Use the [DatabaseConfig] class directly if Kotlin consumer. + */ + public static class Builder { + + Class databaseClass; + OpenHelperCreator openHelperCreator; + + TransactionManagerCreator transactionManagerCreator = null; + DatabaseCallback callback = null; + Map, TableConfig> tableConfigMap = new HashMap<>(); + ModelNotifier modelNotifier = null; + boolean inMemory = false; + String databaseName = null; + String databaseExtensionName = null; + DBFlowDatabase.JournalMode journalMode = DBFlowDatabase.JournalMode.Automatic; + + public Builder(Class databaseClass, OpenHelperCreator openHelperCreator) { + this.databaseClass = databaseClass; + this.openHelperCreator = openHelperCreator; + } + + public Builder transactionManagerCreator(TransactionManagerCreator creator) { + this.transactionManagerCreator = creator; + return this; + } + + public Builder helperListener(DatabaseCallback callback) { + this.callback = callback; + return this; + } + + public Builder addTableConfig(TableConfig tableConfig) { + tableConfigMap.put(tableConfig.tableClass, tableConfig); + return this; + } + + public Builder table(Class clazz, Function, Void> fn) { + TableConfig.Builder builder = TableConfig.builder(clazz); + fn.apply(builder); + return addTableConfig(builder.build()); + } + + public Builder modelNotifier(ModelNotifier modelNotifier) { + this.modelNotifier = modelNotifier; + return this; + } + + public Builder inMemory() { + inMemory = true; + return this; + } + + public Builder journalMode(DBFlowDatabase.JournalMode journalMode) { + this.journalMode = journalMode; + return this; + } + + /** + * @return Pass in dynamic database name here. Otherwise it defaults to class name. + */ + public Builder databaseName(String name) { + databaseName = name; + return this; + } + + /** + * @return Pass in the extension for the DB here. + * Otherwise defaults to ".db". If empty string passed, no extension is used. + */ + public Builder extensionName(String name) { + databaseExtensionName = name; + return this; + } + + public DatabaseConfig build() { + return new DatabaseConfig(this); + } + } + + public interface OpenHelperCreator { + OpenHelper createHelper(DBFlowDatabase db, DatabaseCallback callback); + } + + public interface TransactionManagerCreator { + BaseTransactionManager createManager(DBFlowDatabase db); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/config/DatabaseHolder.kt b/lib/src/main/java/com/dbflow5/config/DatabaseHolder.java similarity index 33% rename from lib/src/main/kotlin/com/dbflow5/config/DatabaseHolder.kt rename to lib/src/main/java/com/dbflow5/config/DatabaseHolder.java index 38d15f40e06ea3447c6adf21f9a30d427fc3a133..797a7b065800f15992e1b92d795c2a3a6e2aabb0 100644 --- a/lib/src/main/kotlin/com/dbflow5/config/DatabaseHolder.kt +++ b/lib/src/main/java/com/dbflow5/config/DatabaseHolder.java @@ -1,43 +1,56 @@ -package com.dbflow5.config +package com.dbflow5.config; -import com.dbflow5.converter.TypeConverter +import com.dbflow5.converter.TypeConverters; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Description: The base interface for interacting with all of the database and top-level data that's shared * between them. */ -abstract class DatabaseHolder { +public abstract class DatabaseHolder { - val databaseDefinitionMap: MutableMap, DBFlowDatabase> = hashMapOf() - val databaseNameMap: MutableMap = hashMapOf() - val databaseClassLookupMap: MutableMap, DBFlowDatabase> = hashMapOf() + Map, DBFlowDatabase> databaseDefinitionMap = new HashMap<>(); + Map databaseNameMap = new HashMap<>(); + Map, DBFlowDatabase> databaseClassLookupMap = new HashMap<>(); - @JvmField - val typeConverters: MutableMap, TypeConverter<*, *>> = hashMapOf() + Map, TypeConverters.TypeConverter> typeConverters = new HashMap<>(); - val databaseDefinitions: List - get() = databaseNameMap.values.toList() + public List databaseDefinitions(){ + return new ArrayList<>(databaseNameMap.values()); + } /** * @param clazz The model value class to get a [TypeConverter] * @return Type converter for the specified model value. */ - fun getTypeConverterForClass(clazz: Class<*>): TypeConverter<*, *>? = typeConverters[clazz] + public TypeConverters.TypeConverter getTypeConverterForClass(Class clazz){ + return typeConverters.get(clazz); + } /** * @param table The model class * @return The database that the table belongs in */ - fun getDatabaseForTable(table: Class<*>): DBFlowDatabase? = databaseDefinitionMap[table] + public DBFlowDatabase getDatabaseForTable(Class table){ + return databaseDefinitionMap.get(table); + } + + public DBFlowDatabase getDatabase(Class databaseClass){ + return databaseClassLookupMap.get(databaseClass); + } - fun getDatabase(databaseClass: Class<*>): DBFlowDatabase? = - databaseClassLookupMap[databaseClass] /** * @param databaseName The name of the database to retrieve * @return The database that has the specified name */ - fun getDatabase(databaseName: String): DBFlowDatabase? = databaseNameMap[databaseName] + public DBFlowDatabase getDatabase(String databaseName){ + return databaseNameMap.get(databaseName); + } /** * Helper method used to store a database for the specified table. @@ -45,16 +58,16 @@ abstract class DatabaseHolder { * @param table The model table * @param databaseDefinition The database definition */ - fun putDatabaseForTable(table: Class<*>, databaseDefinition: DBFlowDatabase) { - databaseDefinitionMap.put(table, databaseDefinition) - databaseNameMap.put(databaseDefinition.databaseName, databaseDefinition) - databaseClassLookupMap.put(databaseDefinition.associatedDatabaseClassFile, databaseDefinition) + public void putDatabaseForTable(Class table, DBFlowDatabase databaseDefinition) { + databaseDefinitionMap.put(table, databaseDefinition); + databaseNameMap.put(databaseDefinition.getDatabaseName(), databaseDefinition); + databaseClassLookupMap.put(databaseDefinition.associatedDatabaseClassFile(), databaseDefinition); } - fun reset() { - databaseDefinitionMap.clear() - databaseNameMap.clear() - databaseClassLookupMap.clear() - typeConverters.clear() + public void reset() { + databaseDefinitionMap.clear(); + databaseNameMap.clear(); + databaseClassLookupMap.clear(); + typeConverters.clear(); } } diff --git a/lib/src/main/java/com/dbflow5/config/FlowConfig.java b/lib/src/main/java/com/dbflow5/config/FlowConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..553bedcb37a9ef3a71afb7dc1e1cc5b80a39eee9 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/config/FlowConfig.java @@ -0,0 +1,114 @@ +package com.dbflow5.config; + +import ohos.app.Context; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * Description: The main configuration instance for DBFlow. This + */ +public class FlowConfig { + + Context context; + Set> databaseHolders; + Map, DatabaseConfig> databaseConfigMap; + boolean openDatabasesOnInit; + + public FlowConfig(Context context, Set> databaseHolders, Map, DatabaseConfig> databaseConfigMap, boolean openDatabasesOnInit) { + this.context = context; + this.databaseHolders = databaseHolders; + this.databaseConfigMap = databaseConfigMap; + this.openDatabasesOnInit = openDatabasesOnInit; + } + + public FlowConfig(Builder builder) { + this.context = builder.context; + this.databaseHolders = builder.databaseHolders; + this.databaseConfigMap = builder.databaseConfigMap; + this.openDatabasesOnInit = builder.openDatabasesOnInit; + } + + public DatabaseConfig getConfigForDatabase(Class databaseClass) { + return databaseConfigMap.get(databaseClass); + } + + /** + * Merges two [FlowConfig] together by combining an existing config. Any new specified [DatabaseConfig] + * will override existing ones. + */ + public FlowConfig merge(FlowConfig flowConfig) { + databaseConfigMap.forEach((aClass, databaseConfig) -> { + if (flowConfig.databaseConfigMap.get(aClass) != null) { + databaseConfigMap.put(aClass, flowConfig.databaseConfigMap.get(aClass)); + } + }); + databaseHolders.addAll(flowConfig.databaseHolders); + return new FlowConfig(flowConfig.context, databaseHolders, databaseConfigMap, flowConfig.openDatabasesOnInit); + } + + public static class Builder { + public Builder(Context context) { + this.context = context.getApplicationContext(); + } + + private final Context context; + private final Set> databaseHolders = new HashSet<>(); + private final Map, DatabaseConfig> databaseConfigMap = new HashMap<>(); + private boolean openDatabasesOnInit = false; + + public Builder addDatabaseHolder(Class databaseHolderClass) { + databaseHolders.add(databaseHolderClass); + return this; + } + + public Builder databaseHolder(Class clazz) { + return addDatabaseHolder(clazz); + } + + public Builder database(DatabaseConfig databaseConfig) { + databaseConfigMap.put(databaseConfig.databaseClass, databaseConfig); + return this; + } + + //fn: DatabaseConfig.Builder.() -> Unit = {} + public Builder database(Class clazz, Function fn, DatabaseConfig.OpenHelperCreator openHelperCreator) { + DatabaseConfig.Builder builder = DatabaseConfig.builder(clazz, openHelperCreator); + fn.apply(builder); + return database(builder.build()); + } + + public Builder inMemoryDatabase(Class clazz, Function fn, DatabaseConfig.OpenHelperCreator openHelperCreator) { + return database(clazz, builder -> { + builder.inMemory(); + return null; + }, openHelperCreator); + } + + /** + * @param openDatabasesOnInit true if we want all databases open. + * @return True to open all associated databases in DBFlow on calling of [FlowManager.init] + */ + public Builder openDatabasesOnInit(boolean openDatabasesOnInit) { + this.openDatabasesOnInit = openDatabasesOnInit; + return this; + } + + public FlowConfig build() { + return new FlowConfig(this); + } + } + + public static Builder builder(Context context) { + return new Builder(context); + } + + public static FlowConfig flowConfig(Context context, Function fn) { + FlowConfig.Builder builder = builder(context); + fn.apply(builder); + return builder.build(); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/config/FlowLog.kt b/lib/src/main/java/com/dbflow5/config/FlowLog.java similarity index 42% rename from lib/src/main/kotlin/com/dbflow5/config/FlowLog.kt rename to lib/src/main/java/com/dbflow5/config/FlowLog.java index bba044ac9b8e4d21ddd114ebce6f1c5dd6758c91..1072ee21c9b8ed20f8c04648329596fcdc360c04 100644 --- a/lib/src/main/kotlin/com/dbflow5/config/FlowLog.kt +++ b/lib/src/main/java/com/dbflow5/config/FlowLog.java @@ -1,26 +1,34 @@ -package com.dbflow5.config +package com.dbflow5.config; -import android.os.Build -import android.util.Log -import com.dbflow5.config.FlowLog.Level +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; /** * Description: Mirrors [Log] with its own [Level] flag. */ -object FlowLog { +public class FlowLog { + private static final HiLogLabel LABEL = + new HiLogLabel(HiLog.LOG_APP, 0x00111, FlowLog.class.getSimpleName()); - val TAG = "FlowLog" - private var level = Level.E + private FlowLog(){ + } + + private static Level level = Level.E; /** * Sets the minimum level that we wish to print out log statements with. * The default is [Level.E]. * - * @param level + * @param level level */ - @JvmStatic - fun setMinimumLoggingLevel(level: Level) { - FlowLog.level = level + public static void setMinimumLoggingLevel(Level level) { + FlowLog.level = level; + } + + public static void log(Level level, HiLogLabel tag, String message) { + if (isEnabled(level)) { + level.call(tag, message, null); + } } /** @@ -31,41 +39,44 @@ object FlowLog { * @param message The message to print out * @param throwable The optional stack trace to print */ - @JvmOverloads - @JvmStatic - fun log(level: Level, tag: String = TAG, message: String? = "", throwable: Throwable? = null) { + public static void log(Level level, HiLogLabel tag, String message, Throwable throwable) { if (isEnabled(level)) { - level.call(tag, message, throwable) + level.call(tag, message, throwable); } } + public static void log(Level level, String message){ + log(level, LABEL, message, null); + } + /** * Logs information to the [Log] class. It wraps around the standard implementation. * * @param level The log level to use - * @param tag The tag of the log * @param message The message to print out * @param throwable The optional stack trace to print */ - @JvmStatic - fun log(level: Level, message: String, throwable: Throwable?) = log(level = level, tag = TAG, message = message, throwable = throwable) + public static void log(Level level, String message, Throwable throwable){ + log(level, LABEL, message, throwable); + } /** * Returns true if the logging level is lower than the specified [Level] * - * @return + * @param level level + * @return isEnable */ - @JvmStatic - fun isEnabled(level: Level) = level.ordinal >= FlowLog.level.ordinal + public static boolean isEnabled(Level level){ + return level.ordinal() >= FlowLog.level.ordinal(); + } /** * Logs a [Throwable] as an error. * * @param throwable The stack trace to print */ - @JvmStatic - fun logError(throwable: Throwable) { - log(Level.E, throwable = throwable) + public static void logError(Throwable throwable) { + log(Level.E, LABEL, "", throwable); } /** @@ -73,52 +84,53 @@ object FlowLog { * * @param throwable The stack trace to print */ - @JvmStatic - fun logWarning(throwable: Throwable) { - log(Level.W, throwable = throwable) + public static void logWarning(Throwable throwable) { + log(Level.W, LABEL, "",throwable); } /** * Defines a log level that will execute */ - enum class Level { + public enum Level { V { - override fun call(tag: String, message: String?, throwable: Throwable?) { - Log.v(tag, message, throwable) + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.info(tag, message); } }, D { - override fun call(tag: String, message: String?, throwable: Throwable?) { - Log.d(tag, message, throwable) + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.debug(tag, message); } + }, I { - override fun call(tag: String, message: String?, throwable: Throwable?) { - Log.i(tag, message, throwable) + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.info(tag, message); } }, W { - override fun call(tag: String, message: String?, throwable: Throwable?) { - Log.w(tag, message, throwable) + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.warn(tag, message); } }, E { - override fun call(tag: String, message: String?, throwable: Throwable?) { - Log.e(tag, message, throwable) + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.error(tag, message); } }, WTF { - override fun call(tag: String, message: String?, throwable: Throwable?) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { - Log.wtf(tag, message, throwable) - } else { - // If on older platform, we will just exaggerate the log message in the error level - Log.e(tag, "!!!!!!!!*******$message********!!!!!!", throwable) - } + @Override + void call(HiLogLabel tag, String message, Throwable throwable) { + HiLog.fatal(tag, message); } }; - internal abstract fun call(tag: String, message: String?, throwable: Throwable?) + abstract void call(HiLogLabel tag, String message, Throwable throwable); } } diff --git a/lib/src/main/java/com/dbflow5/config/FlowManager.java b/lib/src/main/java/com/dbflow5/config/FlowManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1a367315a656ccef2fa310a8eb08b803c2e9269a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/config/FlowManager.java @@ -0,0 +1,508 @@ +package com.dbflow5.config; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.ModelViewAdapter; +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.annotation.Table; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.migration.Migration; +import com.dbflow5.runtime.ModelNotifier; +import com.dbflow5.runtime.TableNotifierRegister; +import com.dbflow5.structure.BaseModel; +import com.dbflow5.structure.BaseModelView; +import com.dbflow5.structure.BaseQueryModel; +import com.dbflow5.structure.InvalidDBConfiguration; +import com.dbflow5.structure.Model; +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.app.Context; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +/** + * Description: The main entry point into the generated database code. It uses reflection to look up + * and construct the generated database holder class used in defining the structure for all databases + * used in this application. + */ +public class FlowManager { + private static final String DEFAULT_DATABASE_HOLDER_NAME = "GeneratedDatabaseHolder"; + //@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + private static final String DEFAULT_DATABASE_HOLDER_PACKAGE_NAME = FlowManager.class.getPackage().getName(); + private static final String DEFAULT_DATABASE_HOLDER_CLASSNAME = DEFAULT_DATABASE_HOLDER_PACKAGE_NAME+"."+DEFAULT_DATABASE_HOLDER_NAME; + + private static FlowConfig config = null; + + private static GlobalDatabaseHolder globalDatabaseHolder = new GlobalDatabaseHolder(); + + private static final Set> loadedModules = new HashSet<>(); + + + /** + * Override for testing + */ + //@set:TestOnly + public static DataAbilityHelper globalContentResolver = null; + + /** + * Will throw an exception if this class is not initialized yet in [.init] + * + * @return The shared context. + */ + public static Context getContext(){ + if(config != null){ + return config.context; + } + throw new IllegalStateException("You must provide a valid FlowConfig instance." + + " We recommend calling init() in your application class."); + } + + public static DataAbilityHelper contentResolver(){ + if(globalContentResolver == null){ + return DataAbilityHelper.creator(getContext()); + } + return globalContentResolver; + } + + private static class GlobalDatabaseHolder extends DatabaseHolder { + + boolean isInitialized = false; + + void add(DatabaseHolder holder) { + databaseDefinitionMap.putAll(holder.databaseDefinitionMap); + databaseNameMap.putAll(holder.databaseNameMap); + typeConverters.putAll(holder.typeConverters); + databaseClassLookupMap.putAll(holder.databaseClassLookupMap); + isInitialized = true; + } + } + + /** + * Returns the table name for the specific model class + * + * @param table The class that implements [Model] + * @return The table name, which can be different than the [Model] class name + */ + public static String getTableName(Class table) { + String name = getModelAdapterOrNull(table).getName(); + if(name == null){ + name = getModelViewAdapterOrNull(table).getName(); + } + if(name == null){ + throw new IllegalArgumentException("Cannot find "+"ModelAdapter/ModelViewAdapter/VirtualAdapter"+" for "+table+". Ensure the class is annotated with proper annotation."); + } + return name; + } + + /** + * @param databaseName The name of the database. Will throw an exception if the databaseForTable doesn't exist. + * @param tableName The name of the table in the DB. + * @return The associated table class for the specified name. + */ + public static Class getTableClassForName(String databaseName, String tableName) { + DBFlowDatabase databaseDefinition = getDatabase(databaseName); + Class clazz = databaseDefinition.getModelClassForName(tableName); + if(clazz == null){ + clazz = databaseDefinition.getModelClassForName(StringUtils.quote(tableName)); + } + if(clazz == null){ + throw new IllegalArgumentException("The specified table "+tableName+" was not found." + + " Did you forget to add the @Table annotation and point it to "+databaseName+"?"); + } + return clazz; + } + + /** + * @param databaseClass The class of the database. Will throw an exception if the databaseForTable doesn't exist. + * @param tableName The name of the table in the DB. + * @return The associated table class for the specified name. + */ + public static Class getTableClassForName(Class databaseClass, String tableName) { + DBFlowDatabase databaseDefinition = getDatabase(databaseClass); + Class clazz = databaseDefinition.getModelClassForName(tableName); + if(clazz == null){ + clazz = databaseDefinition.getModelClassForName(StringUtils.quote(tableName)); + } + if(clazz == null){ + throw new IllegalArgumentException("The specified table "+tableName+" was not found." + + " Did you forget to add the @Table annotation and point it to "+databaseClass+"?"); + } + return clazz; + } + + /** + * @param table The table to lookup the database for. + * @return the corresponding [DBFlowDatabase] for the specified model + */ + public static DBFlowDatabase getDatabaseForTable(Class table) { + checkDatabaseHolder(); + DBFlowDatabase dbFlowDatabase = globalDatabaseHolder.getDatabaseForTable(table); + if(dbFlowDatabase == null){ + throw new InvalidDBConfiguration("Model object: "+table+" is not registered with a Database." + + " Did you forget an annotation?"); + } + return dbFlowDatabase; + } + + public static T getDatabase(Class databaseClass) { + checkDatabaseHolder(); + + T t = (T) globalDatabaseHolder.getDatabase(databaseClass); + if(t == null){ + throw new InvalidDBConfiguration("Database: "+databaseClass.getName()+" is not a registered Database. " + + "Did you forget the @Database annotation?"); + } + return t; + } + + public static String getDatabaseName(Class database) { + return getDatabase(database).getDatabaseName(); + } + + /** + * @param databaseName The name of the database. Will throw an exception if the databaseForTable doesn't exist. + * @return the [DBFlowDatabase] for the specified database + */ + public static DBFlowDatabase getDatabase(String databaseName) { + checkDatabaseHolder(); + DBFlowDatabase dbFlowDatabase = globalDatabaseHolder.getDatabase(databaseName); + if(dbFlowDatabase == null){ + throw new InvalidDBConfiguration("The specified database "+databaseName+" was not found. " + + "Did you forget the @Database annotation?"); + } + return dbFlowDatabase; + } + + @Deprecated + public static DatabaseWrapper getWritableDatabaseForTable(Class table) { + return getDatabaseForTable(table).getWritableDatabase(); + } + + @Deprecated + public static DatabaseWrapper getWritableDatabase(String databaseName) { + return getDatabase(databaseName).getWritableDatabase(); + } + + @Deprecated + public static DatabaseWrapper getWritableDatabase(Class databaseClass){ + return getDatabase(databaseClass).getWritableDatabase(); + } + + /** + * Loading the module Database holder via reflection. + * + * + * It is assumed FlowManager.init() is called by the application that uses the + * module database. This method should only be called if you need to load databases + * that are part of a module. Building once will give you the ability to add the class. + */ + public static void initModule(Class generatedClassName) { + loadDatabaseHolder(generatedClassName); + } + + public static FlowConfig getConfig() { + if(config != null){ + return config; + } + throw new IllegalStateException("Configuration is not initialized. " + + "Please call init(FlowConfig) in your application class."); + } + + /** + * @return The database holder, creating if necessary using reflection. + */ + private static void loadDatabaseHolder(Class holderClass) { + if (loadedModules.contains(holderClass)) { + return; + } + + try { + // Load the database holder, and add it to the global collection. + DatabaseHolder dbHolder = holderClass.newInstance(); + if (dbHolder != null) { + globalDatabaseHolder.add(dbHolder); + + // Cache the holder for future reference. + loadedModules.add(holderClass); + } + } catch (Exception e) { + throw new IllegalArgumentException("gao:"+e.getMessage()); + //throw new ModuleNotFoundException("Cannot load " + holderClass, e); + } + + } + + /** + * Resets all databases and associated files. + */ + public synchronized static void reset() { + Collection flowDatabases = globalDatabaseHolder.databaseClassLookupMap.values(); + for(DBFlowDatabase database : flowDatabases){ + database.reset(database.databaseConfig); + } + + globalDatabaseHolder.reset(); + loadedModules.clear(); + } + + /** + * Close all DB files and resets [FlowConfig] and the [GlobalDatabaseHolder]. Brings + * DBFlow back to initial application state. + */ + public synchronized static void close() { + Collection flowDatabases = globalDatabaseHolder.databaseClassLookupMap.values(); + for(DBFlowDatabase database : flowDatabases){ + database.close(); + } + + config = null; + globalDatabaseHolder = new GlobalDatabaseHolder(); + loadedModules.clear(); + } + + /** + * Helper method to simplify the [.init]. Use [.init] to provide + * more customization. + * + * @param context - should be application context, but not necessary as we retrieve it anyways. + */ + //config: FlowConfig.Builder.() -> Unit = {} + public static void init(Context context, Function config) { + init(FlowConfig.flowConfig(context, config)); + } + + /** + * Initializes DBFlow, loading the main application Database holder via reflection one time only. + * This will trigger all creations, updates, and instantiation for each database defined. + * + * @param flowConfig The configuration instance that will help shape how DBFlow gets constructed. + */ + public static void init(FlowConfig flowConfig) { + if(config == null){ + config = flowConfig; + }else { + config = config.merge(flowConfig); + } + + try { + Class defaultHolderClass = (Class)Class.forName("com.dbflow5.config.GeneratedDatabaseHolder"); +// Class defaultHolderClass = (Class)Class.forName(DEFAULT_DATABASE_HOLDER_CLASSNAME); + loadDatabaseHolder(defaultHolderClass); + } catch (ModuleNotFoundException e) { + // Ignore this exception since it means the application does not have its + // own database. The initialization happens because the application is using + // a module that has a database. + FlowLog.log(FlowLog.Level.W, e.getMessage()); + } catch (ClassNotFoundException e) { + // warning if a library uses DBFlow with module support but the app you're using doesn't support it. + FlowLog.log(FlowLog.Level.W, "Could not find the default GeneratedDatabaseHolder"); + } + + for (Class clazz : flowConfig.databaseHolders){ + loadDatabaseHolder(clazz); + } + + if (flowConfig.openDatabasesOnInit) { + for(DBFlowDatabase database : globalDatabaseHolder.databaseDefinitions()){ + database.getWritableDatabase(); + } + } + } + + /** + * @param objectClass A class with an associated type converter. May return null if not found. + * @return The specific [TypeConverter] for the specified class. It defines + * how the custom datatype is handled going into and out of the DB. + */ + public static TypeConverters.TypeConverter getTypeConverterForClass(Class objectClass) { + checkDatabaseHolder(); + return globalDatabaseHolder.getTypeConverterForClass(objectClass); + } + + // region Getters + + /** + * Release reference to context and [FlowConfig] + */ + public synchronized static void destroy() { + Collection flowDatabases = globalDatabaseHolder.databaseClassLookupMap.values(); + for (DBFlowDatabase database : flowDatabases){ + database.destroy(); + } + + config = null; + // Reset the global database holder. + globalDatabaseHolder = new GlobalDatabaseHolder(); + loadedModules.clear(); + } + + /** + * @param modelClass The class that implements [Model] to find an adapter for. + * @return The adapter associated with the class. If its not a [ModelAdapter], + * it checks both the [ModelViewAdapter] and [RetrievalAdapter]. + */ + public static RetrievalAdapter getRetrievalAdapter(Class modelClass) { + RetrievalAdapter retrievalAdapter = getModelAdapterOrNull(modelClass); + if (retrievalAdapter == null) { + retrievalAdapter = getModelViewAdapterOrNull(modelClass); + if(retrievalAdapter == null){ + retrievalAdapter = getQueryModelAdapterOrNull(modelClass); + } + } + if(retrievalAdapter != null){ + return retrievalAdapter; + } + throw new IllegalArgumentException("Cannot find "+"RetrievalAdapter"+" for "+modelClass+". Ensure the class is annotated with proper annotation."); + } + + /** + * @param modelClass The class of the table + * @return The associated model adapter (DAO) that is generated from a [Table] class. Handles + * interactions with the database. This method is meant for internal usage only. + * We strongly prefer you use the built-in methods associated with [Model] and [BaseModel]. + */ + public static ModelAdapter getModelAdapter(Class modelClass){ + ModelAdapter modelAdapter = getModelAdapterOrNull(modelClass); + if(modelAdapter != null){ + return modelAdapter; + } + throw new IllegalArgumentException("Cannot find "+"ModelAdapter"+" for "+modelClass+". Ensure the class is annotated with proper annotation."); + } + + /** + * Returns the model view adapter for a SQLite VIEW. These are only created with the [com.dbflow5.annotation.ModelView] annotation. + * + * @param modelViewClass The class of the VIEW + * @return The model view adapter for the specified model view. + */ + public static ModelViewAdapter getModelViewAdapter(Class modelViewClass) { + ModelViewAdapter modelViewAdapter = getModelViewAdapterOrNull(modelViewClass); + if(modelViewAdapter != null){ + return modelViewAdapter; + } + throw new IllegalArgumentException("Cannot find "+"ModelViewAdapter"+" for "+modelViewClass+". Ensure the class is annotated with proper annotation."); + } + + /** + * Returns the query model adapter for the model class. These are only created with the [T] annotation. + * + * @param queryModelClass The class of the query + * @return The query model adapter for the specified model cursor. + */ + public static RetrievalAdapter getQueryModelAdapter(Class queryModelClass){ + RetrievalAdapter retrievalAdapter = getQueryModelAdapterOrNull(queryModelClass); + if(retrievalAdapter != null){ + return retrievalAdapter; + } + throw new IllegalArgumentException("Cannot find "+"RetrievalAdapter"+" for "+queryModelClass+". Ensure the class is annotated with proper annotation."); + } + + public static ModelNotifier getModelNotifierForTable(Class table) { + return getDatabaseForTable(table).getModelNotifier(); + } + + public static TableNotifierRegister newRegisterForTable(Class table) { + return getModelNotifierForTable(table).newRegister(); + } + + private static ModelAdapter getModelAdapterOrNull(Class modelClass) { + return getDatabaseForTable(modelClass).getModelAdapterForTable(modelClass); + } + + private static ModelViewAdapter getModelViewAdapterOrNull(Class modelClass) { + return getDatabaseForTable(modelClass).getModelViewAdapterForTable(modelClass); + } + + private static RetrievalAdapter getQueryModelAdapterOrNull(Class modelClass) { + return getDatabaseForTable(modelClass).getQueryModelAdapterForQueryClass(modelClass); + } + + /** + * Checks a standard database helper for integrity using quick_check(1). + * + * @param databaseName The name of the database to check. Will thrown an exception if it does not exist. + * @return true if it's integrity is OK. + */ + public static boolean isDatabaseIntegrityOk(String databaseName){ + return getDatabase(databaseName).openHelper().isDatabaseIntegrityOk(); + } + + private static void throwCannotFindAdapter(String type, Class clazz) { + throw new IllegalArgumentException("Cannot find "+type+" for "+clazz+". Ensure the class is annotated with proper annotation."); + } + + private static void checkDatabaseHolder() { + if (!globalDatabaseHolder.isInitialized) { + throw new IllegalStateException("The global databaseForTable holder is not initialized. " + + "Ensure you call FlowManager.init() before accessing the databaseForTable."); + } + } + + // endregion + + /** + * Exception thrown when a database holder cannot load the databaseForTable holder + * for a module. + */ + static class ModuleNotFoundException extends RuntimeException{ + ModuleNotFoundException(String detailMessage, Throwable throwable){ + super(detailMessage, throwable); + } + } + + /** + * Easily get access to its [DBFlowDatabase] directly. + */ + public static T database(Class clazz, Function f){ + T t = FlowManager.getDatabase(clazz); + f.apply(t); + return t; + } + + /** + * Easily get access to its [DBFlowDatabase] directly. + */ + public static DBFlowDatabase databaseForTable(Class clazz, Function f){ + DBFlowDatabase database = FlowManager.getDatabaseForTable(clazz); + f.apply(database); + return database; + } + + + /** + * Easily get its table name. + */ + public static String tableName(Class clazz){ + return FlowManager.getTableName(clazz); + } + + /** + * Easily get its [ModelAdapter]. + */ + public static ModelAdapter modelAdapter(Class clazz){ + return FlowManager.getModelAdapter(clazz); + } + + @Deprecated + public static RetrievalAdapter queryModelAdapter(Class clazz){ + return FlowManager.getQueryModelAdapter(clazz); + } + + /** + * Easily get its [RetrievalAdapter]. + */ + public static RetrievalAdapter retrievalAdapter(Class clazz){ + return FlowManager.getQueryModelAdapter(clazz); + } + + /** + * Easily get its [ModelViewAdapter] + */ + public static ModelViewAdapter modelViewAdapter(Class clazz) { + return FlowManager.getModelViewAdapter(clazz); + } +} + diff --git a/lib/src/main/kotlin/com/dbflow5/config/NaturalOrderComparator.java b/lib/src/main/java/com/dbflow5/config/NaturalOrderComparator.java similarity index 100% rename from lib/src/main/kotlin/com/dbflow5/config/NaturalOrderComparator.java rename to lib/src/main/java/com/dbflow5/config/NaturalOrderComparator.java diff --git a/lib/src/main/java/com/dbflow5/config/TableConfig.java b/lib/src/main/java/com/dbflow5/config/TableConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..769e3e22c7bd041781dd740adc6ef12671c36033 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/config/TableConfig.java @@ -0,0 +1,85 @@ +package com.dbflow5.config; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.adapter.saveable.ModelSaver; + +/** + * Description: Represents certain table configuration options. This allows you to easily specify + * certain configuration options for a table. + */ +public class TableConfig{ + + public Class tableClass; + public ModelSaver modelSaver; + public SingleModelLoader singleModelLoader; + public ListModelLoader listModelLoader; + + public TableConfig(Class tableClass, ModelSaver modelSaver, SingleModelLoader singleModelLoader, ListModelLoader listModelLoader){ + this.tableClass = tableClass; + this.modelSaver = modelSaver; + this.singleModelLoader = singleModelLoader; + this.listModelLoader = listModelLoader; + } + + public TableConfig(Builder builder){ + tableClass = builder.tableClass; + modelSaver = builder.modelAdapterModelSaver; + singleModelLoader = builder.singleModelLoader; + listModelLoader = builder.listModelLoader; + } + + /** + * Table builder for java consumers. use [TableConfig] directly if calling from Kotlin. + */ + static class Builder{ + + private final Class tableClass; + + public ModelSaver modelAdapterModelSaver = null; + public SingleModelLoader singleModelLoader = null; + public ListModelLoader listModelLoader = null; + + Builder(Class tableClass){ + this.tableClass = tableClass; + } + + /** + * Define how the [ModelAdapter] saves data into the DB from its associated [T]. This + * will override the default. + */ + public Builder modelAdapterModelSaver(ModelSaver modelSaver) { + this.modelAdapterModelSaver = modelSaver; + return this; + } + + /** + * Define how the table loads single models. This will override the default. + */ + public Builder singleModelLoader(SingleModelLoader singleModelLoader) { + this.singleModelLoader = singleModelLoader; + return this; + } + + /** + * Define how the table loads a [List] of items. This will override the default. + */ + public Builder listModelLoader(ListModelLoader listModelLoader) { + this.listModelLoader = listModelLoader; + return this; + } + + /** + * @return A new [TableConfig]. Subsequent calls to this method produce a new instance + * of [TableConfig]. + */ + public TableConfig build(){ + return new TableConfig<>(this); + } + } + + public static Builder builder(Class tableClass) { + return new Builder(tableClass); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/database/BaseDatabaseStatement.kt b/lib/src/main/java/com/dbflow5/database/BaseDatabaseStatement.java similarity index 30% rename from lib/src/main/kotlin/com/dbflow5/database/BaseDatabaseStatement.kt rename to lib/src/main/java/com/dbflow5/database/BaseDatabaseStatement.java index f04a59d21b8ec9b3f0de665493ffe4102e5bdc7f..c53b1a58f053084cbfea5ea015662c8056081c2a 100644 --- a/lib/src/main/kotlin/com/dbflow5/database/BaseDatabaseStatement.kt +++ b/lib/src/main/java/com/dbflow5/database/BaseDatabaseStatement.java @@ -1,51 +1,56 @@ -package com.dbflow5.database +package com.dbflow5.database; /** * Description: Default implementation for some [DatabaseStatement] methods. */ -abstract class BaseDatabaseStatement : DatabaseStatement { - - override fun bindStringOrNull(index: Int, s: String?) { +public abstract class BaseDatabaseStatement implements DatabaseStatement { + @Override + public void bindStringOrNull(int index, String s) { if (s != null) { - bindString(index, s) + bindString(index, s); } else { - bindNull(index) + bindNull(index); } } - override fun bindNumber(index: Int, number: Number?) { - bindNumberOrNull(index, number) + @Override + public void bindNumber(int index, Number number) { + bindNumberOrNull(index, number); } - override fun bindNumberOrNull(index: Int, number: Number?) { + @Override + public void bindNumberOrNull(int index, Number number) { if (number != null) { - bindLong(index, number.toLong()) + bindLong(index, number.longValue()); } else { - bindNull(index) + bindNull(index); } } - override fun bindDoubleOrNull(index: Int, aDouble: Double?) { + @Override + public void bindDoubleOrNull(int index, Double aDouble) { if (aDouble != null) { - bindDouble(index, aDouble) + bindDouble(index, aDouble); } else { - bindNull(index) + bindNull(index); } } - override fun bindFloatOrNull(index: Int, aFloat: Float?) { + @Override + public void bindFloatOrNull(int index, Float aFloat) { if (aFloat != null) { - bindDouble(index, aFloat.toDouble()) + bindDouble(index, aFloat.doubleValue()); } else { - bindNull(index) + bindNull(index); } } - override fun bindBlobOrNull(index: Int, bytes: ByteArray?) { + @Override + public void bindBlobOrNull(int index, byte[] bytes) { if (bytes != null) { - bindBlob(index, bytes) + bindBlob(index, bytes); } else { - bindNull(index) + bindNull(index); } } diff --git a/lib/src/main/java/com/dbflow5/database/ContentValueExtensions.java b/lib/src/main/java/com/dbflow5/database/ContentValueExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..8e5b00ef6e17783fdd27c99851304fe4797e669d --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/ContentValueExtensions.java @@ -0,0 +1,44 @@ +package com.dbflow5.database; + +import ohos.data.rdb.ValuesBucket; + +class ContentValueExtension{ + public static void set(ValuesBucket valuesBucket, String key, String value) { + valuesBucket.putString(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, byte value) { + valuesBucket.putByte(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, short value) { + valuesBucket.putShort(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, int value) { + valuesBucket.putInteger(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, long value) { + valuesBucket.putLong(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, float value) { + valuesBucket.putFloat(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, double value) { + valuesBucket.putDouble(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, boolean value) { + valuesBucket.putBoolean(key, value); + } + + public static void set(ValuesBucket valuesBucket, String key, byte[] value) { + valuesBucket.putByteArray(key, value); + } +} + + + diff --git a/lib/src/main/kotlin/com/dbflow5/database/DatabaseCallback.kt b/lib/src/main/java/com/dbflow5/database/DatabaseCallback.java similarity index 71% rename from lib/src/main/kotlin/com/dbflow5/database/DatabaseCallback.kt rename to lib/src/main/java/com/dbflow5/database/DatabaseCallback.java index 9f14ed5cfcbc7395a630cf3b7a47e88c23ed5375..762b811f31f7aacf71a22e8c6c13d9192a3f64fc 100644 --- a/lib/src/main/kotlin/com/dbflow5/database/DatabaseCallback.kt +++ b/lib/src/main/java/com/dbflow5/database/DatabaseCallback.java @@ -1,23 +1,23 @@ -package com.dbflow5.database +package com.dbflow5.database; /** * Description: Provides callbacks for [OpenHelper] methods */ -interface DatabaseCallback { +public interface DatabaseCallback { /** * Called when the DB is opened * * @param database The database that is opened */ - fun onOpen(database: DatabaseWrapper) = Unit + void onOpen(DatabaseWrapper database); /** * Called when the DB is created * * @param database The database that is created */ - fun onCreate(database: DatabaseWrapper) = Unit + void onCreate(DatabaseWrapper database); /** * Called when the DB is upgraded. @@ -26,7 +26,7 @@ interface DatabaseCallback { * @param oldVersion The previous DB version * @param newVersion The new DB version */ - fun onUpgrade(database: DatabaseWrapper, oldVersion: Int, newVersion: Int) = Unit + void onUpgrade(DatabaseWrapper database, int oldVersion, int newVersion); /** * Called when DB is downgraded. Note that this may not be supported by all implementations of the DB. @@ -35,11 +35,11 @@ interface DatabaseCallback { * @param oldVersion The old. higher version. * @param newVersion The new lower version. */ - fun onDowngrade(databaseWrapper: DatabaseWrapper, oldVersion: Int, newVersion: Int) = Unit + void onDowngrade(DatabaseWrapper databaseWrapper, int oldVersion, int newVersion); /** * Called when DB connection is being configured. Useful for checking foreign key support or enabling * write-ahead-logging. */ - fun onConfigure(db: DatabaseWrapper) = Unit + void onConfigure(DatabaseWrapper db); } diff --git a/lib/src/main/java/com/dbflow5/database/DatabaseStatement.java b/lib/src/main/java/com/dbflow5/database/DatabaseStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..9bccfda230b5661f68b9887434d7945922841e41 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/DatabaseStatement.java @@ -0,0 +1,46 @@ +package com.dbflow5.database; + +import java.io.Closeable; + +/** + * Description: Abstracts out an Android SQLiteStatement. + */ +public interface DatabaseStatement extends Closeable { + + long executeUpdateDelete(); + + void execute(); + + @Override + void close(); + + long simpleQueryForLong(); + + String simpleQueryForString(); + + long executeInsert(); + + void bindString(int index, String s); + + void bindStringOrNull(int index, String s); + + void bindNull(int index); + + void bindLong(int index, Long aLong); + + void bindNumber(int index, Number number); + + void bindNumberOrNull(int index, Number number); + + void bindDouble(int index, Double aDouble); + + void bindDoubleOrNull(int index, Double aDouble); + + void bindFloatOrNull(int index, Float aFloat); + + void bindBlob(int index, byte[] bytes); + + void bindBlobOrNull(int index, byte[] bytes); + + void bindAllArgsAsStrings(String[] selectionArgs); +} diff --git a/lib/src/main/java/com/dbflow5/database/DatabaseStatementWrapper.java b/lib/src/main/java/com/dbflow5/database/DatabaseStatementWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..699ebe3d12864418b8e66fe40d7ae1e0165d430e --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/DatabaseStatementWrapper.java @@ -0,0 +1,87 @@ +package com.dbflow5.database; + +import com.dbflow5.query.BaseQueriable; +import com.dbflow5.runtime.NotifyDistributor; + +/** + * Description: Delegates all of its calls to the contained [DatabaseStatement], while + * providing notification methods for when operations occur. + */ +public class DatabaseStatementWrapper extends BaseDatabaseStatement { + + private final DatabaseStatement databaseStatement; + private final BaseQueriable modelQueriable; + + public DatabaseStatementWrapper(DatabaseStatement databaseStatement, BaseQueriable modelQueriable) { + this.databaseStatement = databaseStatement; + this.modelQueriable = modelQueriable; + } + + @Override + public long executeUpdateDelete() { + long affected = databaseStatement.executeUpdateDelete(); + if (affected > 0) { + NotifyDistributor.get().notifyTableChanged(modelQueriable.table, modelQueriable.primaryAction()); + } + return affected; + } + + @Override + public long executeInsert() { + long affected = databaseStatement.executeInsert(); + if (affected > 0) { + NotifyDistributor.get().notifyTableChanged(modelQueriable.table, modelQueriable.primaryAction()); + } + return affected; + } + + @Override + public void execute() { + databaseStatement.execute(); + } + + @Override + public void close() { + databaseStatement.close(); + } + + @Override + public long simpleQueryForLong() { + return databaseStatement.simpleQueryForLong(); + } + + @Override + public String simpleQueryForString() { + return databaseStatement.simpleQueryForString(); + } + + @Override + public void bindString(int index, String s) { + databaseStatement.bindString(index, s); + } + + @Override + public void bindNull(int index) { + databaseStatement.bindNull(index); + } + + @Override + public void bindLong(int index, Long aLong) { + databaseStatement.bindLong(index, aLong); + } + + @Override + public void bindDouble(int index, Double aDouble) { + databaseStatement.bindDouble(index, aDouble); + } + + @Override + public void bindBlob(int index, byte[] bytes) { + databaseStatement.bindBlob(index, bytes); + } + + @Override + public void bindAllArgsAsStrings(String[] selectionArgs) { + databaseStatement.bindAllArgsAsStrings(selectionArgs); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/database/DatabaseWrapper.kt b/lib/src/main/java/com/dbflow5/database/DatabaseWrapper.java similarity index 36% rename from lib/src/main/kotlin/com/dbflow5/database/DatabaseWrapper.kt rename to lib/src/main/java/com/dbflow5/database/DatabaseWrapper.java index 1b3e53b7df8f2d1ef248a352b883f4b80b6ab31e..914721872db52756d8bf062198a9cbaab5d03705 100644 --- a/lib/src/main/kotlin/com/dbflow5/database/DatabaseWrapper.kt +++ b/lib/src/main/java/com/dbflow5/database/DatabaseWrapper.java @@ -1,71 +1,92 @@ -package com.dbflow5.database +package com.dbflow5.database; + +import java.util.function.Function; /** * Description: Provides a base implementation that wraps a database, so other databaseForTable engines potentially can * be used. */ -interface DatabaseWrapper { +public interface DatabaseWrapper { - val isInTransaction: Boolean + /** + * isInTransaction + * + * @return is or not in transaction + */ + boolean isInTransaction(); /** * The current version of the database. + * + * @return version */ - val version: Int + int getVersion(); /** * Execute an arbitrary SQL query. + * + * @param query sql */ - fun execSQL(query: String) + void execSQL(String query); /** * Begin a transaction. */ - fun beginTransaction() + void beginTransaction(); /** * Set when a transaction complete successfully to preserve db state. */ - fun setTransactionSuccessful() + void setTransactionSuccessful(); /** * Always called whenever transaction should complete. If [setTransactionSuccessful] is not called, * db state will not be preserved. */ - fun endTransaction() + void endTransaction(); /** * For a given query, return a [DatabaseStatement]. + * + * @param rawQuery sql + * @return DatabaseStatement */ - fun compileStatement(rawQuery: String): DatabaseStatement + DatabaseStatement compileStatement(String rawQuery); /** * For a given query and selectionArgs, return a [DatabaseStatement]. + * + * @param rawQuery sql + * @param selectionArgs args + * @return DatabaseStatement */ - fun compileStatement(rawQuery: String, selectionArgs: Array?): DatabaseStatement { - return compileStatement(rawQuery).apply { bindAllArgsAsStrings(selectionArgs) } + default DatabaseStatement compileStatement(String rawQuery, String[] selectionArgs){ + DatabaseStatement statement = compileStatement(rawQuery); + statement.bindAllArgsAsStrings(selectionArgs); + return statement; } /** * For given query and selection args, return a [FlowCursor] to retrieve data. */ - fun rawQuery(query: String, selectionArgs: Array?): FlowCursor + FlowCursor rawQuery(String query, String[] selectionArgs); - fun query(tableName: String, columns: Array?, selection: String?, - selectionArgs: Array?, groupBy: String?, - having: String?, orderBy: String?): FlowCursor + FlowCursor query(String tableName, String[] columns, String[] selectionColumns, + String[] selectionArgs, String groupBy, + String having, String orderBy); - fun delete(tableName: String, whereClause: String?, whereArgs: Array?): Int + int delete(String tableName, String[] whereClause, String[] whereArgs); -} - -inline fun DatabaseWrapper.executeTransaction(dbFn: DatabaseWrapper.() -> Unit) { - try { - beginTransaction() - dbFn() - setTransactionSuccessful() - } finally { - endTransaction() + default void executeTransaction(Function dbFn) { + try { + beginTransaction(); + dbFn.apply(null); + setTransactionSuccessful(); + } finally { + endTransaction(); + } } } + + diff --git a/lib/src/main/java/com/dbflow5/database/FlowCursor.java b/lib/src/main/java/com/dbflow5/database/FlowCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..69f5fba0a5440045f1263072f4ce59bb91ae2215 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/FlowCursor.java @@ -0,0 +1,232 @@ +package com.dbflow5.database; + +import ohos.data.resultset.ResultSet; +import ohos.data.resultset.ResultSetWrapper; + +/** + * Common [Cursor] class that wraps cursors we use in this library with convenience loading methods. + * This is used to help cut down on generated code size and potentially decrease method count. + */ +public class FlowCursor extends ResultSetWrapper { + public static final int COLUMN_NOT_FOUND = -1; + + private final ResultSet cursor; + + private FlowCursor(ResultSet cursor) { + super(cursor); + this.cursor = cursor; + } + + @Override + public ResultSet getResultSet() { + return cursor; + } + + public String getStringOrDefault(int index, String defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getString(index); + } else { + return defValue; + } + } + + public String getStringOrDefault(String columnName) { + return getStringOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public String getStringOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getString(index); + } else { + return null; + } + } + + public String getStringOrDefault(String columnName, String defValue) { + return getStringOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public int getIntOrDefault(String columnName) { + return getIntOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public int getIntOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getInt(index); + } else { + return 0; + } + } + + public int getIntOrDefault(int index, int defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getInt(index); + } else { + return defValue; + } + } + + public int getIntOrDefault(String columnName, int defValue) { + return getIntOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public double getDoubleOrDefault(int index, double defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getDouble(index); + } else { + return defValue; + } + } + + public double getDoubleOrDefault(String columnName) { + return getDoubleOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public double getDoubleOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getDouble(index); + } else { + return 0.0; + } + } + + public double getDoubleOrDefault(String columnName, double defValue) { + return getDoubleOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public float getFloatOrDefault(int index, float defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getFloat(index); + } else { + return defValue; + } + } + + public float getFloatOrDefault(String columnName) { + return getFloatOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public float getFloatOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getFloat(index); + } else { + return 0f; + } + } + + public float getFloatOrDefault(String columnName, float defValue) { + return getFloatOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public long getLongOrDefault(int index, long defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getLong(index); + } else { + return defValue; + } + } + + public long getLongOrDefault(String columnName) { + return getLongOrDefault(cursor.getColumnIndexForName(columnName)); + } + + public long getLongOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getLong(index); + } else { + return 0L; + } + } + + public long getLongOrDefault(String columnName, long defValue) { + return getLongOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public short getShortOrDefault(int index, short defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getShort(index); + } else { + return defValue; + } + } + + public short getShortOrDefault(String columnName) { + return getShortOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public short getShortOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getShort(index); + } else { + return 0; + } + } + + public short getShortOrDefault(String columnName, short defValue) { + return getShortOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public byte[] getBlobOrDefault(String columnName) { + return getBlobOrDefault(cursor.getColumnIndexForName(columnName)); + } + + + public byte[] getBlobOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getBlob(index); + } else { + return null; + } + } + + public byte[] getBlobOrDefault(int index, byte[] defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return cursor.getBlob(index); + } else { + return defValue; + } + } + + public byte[] getBlobOrDefault(String columnName, byte[] defValue) { + return getBlobOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + public boolean getBooleanOrDefault(int index, boolean defValue) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return getBoolean(index); + } else { + return defValue; + } + } + + public boolean getBooleanOrDefault(String columnName) { + return getBooleanOrDefault(cursor.getColumnIndexForName(columnName)); + } + + public boolean getBooleanOrDefault(int index) { + if (index != COLUMN_NOT_FOUND && !cursor.isColumnNull(index)) { + return getBoolean(index); + } else { + return false; + } + } + + public boolean getBooleanOrDefault(String columnName, boolean defValue) { + return getBooleanOrDefault(cursor.getColumnIndexForName(columnName), defValue); + } + + + public boolean getBoolean(int index) { + return cursor.getInt(index) == 1; + } + + public static FlowCursor from(ResultSet cursor) { + return new FlowCursor(cursor); + } +} + diff --git a/lib/src/main/java/com/dbflow5/database/LocalDatabaseHelper.java b/lib/src/main/java/com/dbflow5/database/LocalDatabaseHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..4de98ab9170178f44c41043e81c03e1e22dd5fd0 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/LocalDatabaseHelper.java @@ -0,0 +1,186 @@ +package com.dbflow5.database; + +import com.dbflow5.adapter.CreationAdapter; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.NaturalOrderComparator; +import com.dbflow5.migration.Migration; +import ohos.aafwk.ability.DataAbilityRemoteException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Description: Manages creation, updating, and migrating ac [DBFlowDatabase]. It performs View creations. + */ +public class LocalDatabaseHelper { + public static final String MIGRATION_PATH = "migrations"; + + private final MigrationFileHelper migrationFileHelper; + DBFlowDatabase databaseDefinition; + + public LocalDatabaseHelper(MigrationFileHelper migrationFileHelper, DBFlowDatabase databaseDefinition){ + this.migrationFileHelper = migrationFileHelper; + this.databaseDefinition = databaseDefinition; + } + + // path to migration for the database. + private String dbMigrationPath(){ + return MIGRATION_PATH + "/" + databaseDefinition.getDatabaseName(); + } + + public void onConfigure(DatabaseWrapper db) { + checkForeignKeySupport(db); + } + + public void onCreate(DatabaseWrapper db) { + // table creations done first to get tables in db. + executeTableCreations(db); + + // execute any initial migrations when DB is first created. + // use the databaseversion of the definition, since onupgrade is not called oncreate on a version 0 + // then SQLCipher and Android set the DB to that version you choose. + executeMigrations(db, -1, databaseDefinition.databaseVersion()); + + // views reflect current db state. + executeViewCreations(db); + } + + public void onUpgrade(DatabaseWrapper db, int oldVersion, int newVersion) { + // create new tables if not previously created + executeTableCreations(db); + + // migrations run to get to DB newest version. adjusting any existing tables to new version + executeMigrations(db, oldVersion, newVersion); + + // views reflect current db state. + executeViewCreations(db); + } + + public void onOpen(DatabaseWrapper db) { + } + + public void onDowngrade(DatabaseWrapper db, int oldVersion, int newVersion) { + } + + /** + * If foreign keys are supported, we turn it on the DB specified. + */ + protected void checkForeignKeySupport(DatabaseWrapper database) { + if (databaseDefinition.isForeignKeysSupported()) { + database.execSQL("PRAGMA foreign_keys=ON;"); + FlowLog.log(FlowLog.Level.I, "Foreign Keys supported. Enabling foreign key features.", null); + } + } + + protected void executeTableCreations(DatabaseWrapper database) { + database.executeTransaction(unused -> { + databaseDefinition.modelAdapters() + .stream() + .sequential() + .filter(CreationAdapter::createWithDatabase) + .forEach(modelAdapter -> { + try { + modelAdapter.createIfNotExists(database); + } catch (SQLiteException e) { + FlowLog.logError(e); + } + }); + return null; + }); + } + + /** + * This method executes CREATE TABLE statements as well as CREATE VIEW on the database passed. + */ + protected void executeViewCreations(DatabaseWrapper database) { + database.executeTransaction(unused -> { + databaseDefinition.modelViewAdapters() + .stream() + .sequential() + .filter(CreationAdapter::createWithDatabase) + .forEach(modelAdapter -> { + try { + modelAdapter.createIfNotExists(database); + } catch (SQLiteException e) { + FlowLog.logError(e); + } + }); + return null; + }); + } + + protected void executeMigrations(DatabaseWrapper db, int oldVersion, int newVersion) { + // will try migrations file or execute migrations from code + List files = migrationFileHelper.getListFiles(dbMigrationPath()); + files.sort(new NaturalOrderComparator()); + + Map> migrationFileMap = new HashMap<>(); + for (String file : files) { + try { + int version = Integer.parseInt(file.replace(".sql", "")); + List fileList = migrationFileMap.getOrDefault(version, new ArrayList<>()); + fileList.add(file); + } catch (NumberFormatException e) { + FlowLog.log(FlowLog.Level.W, "Skipping invalidly named file: "+file, e); + } + + } + + Map> migrationMap = databaseDefinition.getMigrations(); + + int curVersion = oldVersion + 1; + + try { + db.beginTransaction(); + + // execute migrations in order, migration file first before wrapped migration classes. + for (int i = curVersion; i<= newVersion; i++){ + List migrationFiles = migrationFileMap.get(i); + if (migrationFiles != null) { + for (String migrationFile : migrationFiles) { + executeSqlScript(db, migrationFile); + FlowLog.log(FlowLog.Level.I, "$migrationFile executed successfully."); + } + } + + List migrationsList = migrationMap.get(i); + if (migrationsList != null) { + for (Migration migration : migrationsList) { + // before migration + migration.onPreMigrate(); + + // migrate + migration.migrate(db); + + // after migration cleanup + migration.onPostMigrate(); + FlowLog.log(FlowLog.Level.I, "${migration.javaClass} executed successfully."); + } + } + } + db.setTransactionSuccessful(); + } catch (DataAbilityRemoteException e) { + e.printStackTrace(); + } finally { + db.endTransaction(); + } + + } + + /** + * Supports multiline sql statements with ended with the standard ";" + * + * @param db The database to run it on + * @param file the file name in assets/migrations that we read from + */ + private void executeSqlScript(DatabaseWrapper db, String file) { + migrationFileHelper.executeMigration(dbMigrationPath() + "/" + file, queryString -> { + db.execSQL(queryString); + return null; + }); + } + +} diff --git a/lib/src/main/kotlin/com/dbflow5/database/DatabaseHelperDelegate.kt b/lib/src/main/java/com/dbflow5/database/LocalDatabaseHelperDelegate.java similarity index 36% rename from lib/src/main/kotlin/com/dbflow5/database/DatabaseHelperDelegate.kt rename to lib/src/main/java/com/dbflow5/database/LocalDatabaseHelperDelegate.java index 51b58b467144963bed6e59faf2376f98ea9f7952..ca2e14783b70afa30cb0090d0cc194e84707a5fe 100644 --- a/lib/src/main/kotlin/com/dbflow5/database/DatabaseHelperDelegate.kt +++ b/lib/src/main/java/com/dbflow5/database/LocalDatabaseHelperDelegate.java @@ -1,32 +1,39 @@ -package com.dbflow5.database - -import android.content.Context -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowLog -import com.dbflow5.transaction.DefaultTransactionQueue -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream +package com.dbflow5.database; + +import com.dbflow5.DatabaseFileUtils; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowLog; +import ohos.app.Context; +import ohos.data.DatabaseHelper; + +import java.io.*; +import java.util.function.Function; /** * Description: An abstraction from some parts of the Android SQLiteOpenHelper where this can be * used in other helper class definitions. */ -class DatabaseHelperDelegate( - private val context: Context, - private var databaseCallback: DatabaseCallback?, - databaseDefinition: DBFlowDatabase, - private val backupHelper: OpenHelper?) - : DatabaseHelper(AndroidMigrationFileHelper(context), databaseDefinition), OpenHelperDelegate { +public class LocalDatabaseHelperDelegate extends LocalDatabaseHelper implements OpenHelperDelegate { + public static final String TEMP_DB_NAME = "temp-"; + + private final Context context; + private DatabaseCallback databaseCallback; + private final OpenHelper backupHelper; + + public LocalDatabaseHelperDelegate(Context context, DatabaseCallback databaseCallback, DBFlowDatabase databaseDefinition, OpenHelper backupHelper){ + super(new OhosMigrationFileHelper(context), databaseDefinition); + this.context = context; + this.databaseCallback = databaseCallback; + this.backupHelper = backupHelper; + } /** * @return the temporary database file name for when we have backups enabled * [DBFlowDatabase.backupEnabled] */ - private val tempDbFileName: String - get() = getTempDbFileName(databaseDefinition) + private String tempDbFileName(){ + return getTempDbFileName(databaseDefinition); + } /** * Pulled partially from code, it runs a "PRAGMA quick_check(1)" to see if the database is ok. @@ -35,59 +42,67 @@ class DatabaseHelperDelegate( * * @return true if the database is ok, false if the consistency has been compromised. */ - override val isDatabaseIntegrityOk: Boolean - get() = isDatabaseIntegrityOk(database) + @Override + public boolean isDatabaseIntegrityOk(){ + return isDatabaseIntegrityOk(database()); + } - override val database: DatabaseWrapper - get() = databaseDefinition + @Override + public DatabaseWrapper database() { + return databaseDefinition; + } - override fun performRestoreFromBackup() { - movePrepackagedDB(databaseDefinition.databaseFileName, - databaseDefinition.databaseFileName) + @Override + public void performRestoreFromBackup() { + movePrepackagedDB(databaseDefinition.getDatabaseFileName(), + databaseDefinition.getDatabaseFileName()); if (databaseDefinition.backupEnabled()) { if (backupHelper == null) { - throw IllegalStateException("the passed backup helper was null, even though backup" + - " is enabled. Ensure that its passed in.") + throw new IllegalStateException("the passed backup helper was null, even though backup" + + " is enabled. Ensure that its passed in."); } - restoreDatabase(tempDbFileName, databaseDefinition.databaseFileName) - backupHelper.database + restoreDatabase(tempDbFileName(), databaseDefinition.getDatabaseFileName()); + backupHelper.database(); } } - override val delegate: DatabaseHelperDelegate = this + @Override + public LocalDatabaseHelperDelegate delegate() { + return this; + } /** * @param databaseCallback Listens for operations the DB and allow you to provide extra * functionality. */ - fun setDatabaseHelperListener(databaseCallback: DatabaseCallback?) { - this.databaseCallback = databaseCallback + public void setDatabaseHelperListener(DatabaseCallback databaseCallback) { + this.databaseCallback = databaseCallback; } - override fun onConfigure(db: DatabaseWrapper) { - databaseCallback?.onConfigure(db) - super.onConfigure(db) + public void onConfigure(DatabaseWrapper db) { + databaseCallback.onConfigure(db); + super.onConfigure(db); } - override fun onCreate(db: DatabaseWrapper) { - databaseCallback?.onCreate(db) - super.onCreate(db) + public void onCreate(DatabaseWrapper db) { + databaseCallback.onCreate(db); + super.onCreate(db); } - override fun onUpgrade(db: DatabaseWrapper, oldVersion: Int, newVersion: Int) { - databaseCallback?.onUpgrade(db, oldVersion, newVersion) - super.onUpgrade(db, oldVersion, newVersion) + public void onUpgrade(DatabaseWrapper db, int oldVersion, int newVersion) { + databaseCallback.onUpgrade(db, oldVersion, newVersion); + super.onUpgrade(db, oldVersion, newVersion); } - override fun onOpen(db: DatabaseWrapper) { - databaseCallback?.onOpen(db) - super.onOpen(db) + public void onOpen(DatabaseWrapper db) { + databaseCallback.onOpen(db); + super.onOpen(db); } - override fun onDowngrade(db: DatabaseWrapper, oldVersion: Int, newVersion: Int) { - databaseCallback?.onDowngrade(db, oldVersion, newVersion) - super.onDowngrade(db, oldVersion, newVersion) + public void onDowngrade(DatabaseWrapper db, int oldVersion, int newVersion) { + databaseCallback.onDowngrade(db, oldVersion, newVersion); + super.onDowngrade(db, oldVersion, newVersion); } /** @@ -97,36 +112,38 @@ class DatabaseHelperDelegate( * @param databaseName The name of the database to copy over * @param prepackagedName The name of the prepackaged db file */ - fun movePrepackagedDB(databaseName: String, prepackagedName: String) { - val dbPath = context.getDatabasePath(databaseName) + public void movePrepackagedDB(String databaseName, String prepackagedName) { + File dbPath = DatabaseFileUtils.getDatabasePath(context, databaseName); // If the database already exists, and is ok return if (dbPath.exists() && (!databaseDefinition.areConsistencyChecksEnabled() - || (databaseDefinition.areConsistencyChecksEnabled() && isDatabaseIntegrityOk(database)))) { - return + || (databaseDefinition.areConsistencyChecksEnabled() && isDatabaseIntegrityOk(database())))) { + return; } // Make sure we have a path to the file - dbPath.parentFile.mkdirs() + dbPath.getParentFile().mkdirs(); // Try to copy database file try { // check existing and use that as backup - val existingDb = context.getDatabasePath(tempDbFileName) + File existingDb = DatabaseFileUtils.getDatabasePath(context, tempDbFileName()); // if it exists and the integrity is ok we use backup as the main DB is no longer valid - val inputStream = if (existingDb.exists() + InputStream inputStream = null; + if (existingDb.exists() && (!databaseDefinition.backupEnabled() || (databaseDefinition.backupEnabled() && backupHelper != null - && isDatabaseIntegrityOk(backupHelper.database)))) { - FileInputStream(existingDb) + && isDatabaseIntegrityOk(backupHelper.database())))) { + inputStream = new FileInputStream(existingDb); } else { - context.assets.open(prepackagedName) + existingDb.createNewFile(); + inputStream = new FileInputStream(existingDb); } - writeDB(dbPath, inputStream) - } catch (e: IOException) { - FlowLog.log(FlowLog.Level.W, "Failed to open file", e) + writeDB(dbPath, inputStream); + } catch (IOException e) { + FlowLog.log(FlowLog.Level.W, "Failed to open file", e); } } @@ -139,32 +156,34 @@ class DatabaseHelperDelegate( * @param databaseName The name of the database to restore * @param prepackagedName The name of the prepackaged db file */ - fun restoreDatabase(databaseName: String, prepackagedName: String) { - val dbPath = context.getDatabasePath(databaseName) + public void restoreDatabase(String databaseName, String prepackagedName) { + File dbPath = DatabaseFileUtils.getDatabasePath(context, databaseName); // If the database already exists, return if (dbPath.exists()) { - return + return; } // Make sure we have a path to the file - dbPath.parentFile.mkdirs() + dbPath.getParentFile().mkdirs(); // Try to copy database file try { // check existing and use that as backup - val existingDb = context.getDatabasePath(databaseDefinition.databaseFileName) + File existingDb = DatabaseFileUtils.getDatabasePath(context, databaseDefinition.getDatabaseFileName()); // if it exists and the integrity is ok - val inputStream = if (existingDb.exists() + InputStream inputStream = null; + if (existingDb.exists() && (databaseDefinition.backupEnabled() && backupHelper != null - && isDatabaseIntegrityOk(backupHelper.database))) { - FileInputStream(existingDb) + && isDatabaseIntegrityOk(backupHelper.database()))) { + inputStream = new FileInputStream(existingDb); } else { - context.assets.open(prepackagedName) + existingDb.createNewFile(); + inputStream = new FileInputStream(existingDb); } - writeDB(dbPath, inputStream) - } catch (e: IOException) { - FlowLog.logError(e) + writeDB(dbPath, inputStream); + } catch (IOException e) { + FlowLog.logError(e); } } @@ -176,43 +195,42 @@ class DatabaseHelperDelegate( * * @return true if the database is ok, false if the consistency has been compromised. */ - fun isDatabaseIntegrityOk(databaseWrapper: DatabaseWrapper): Boolean { - var integrityOk = true - databaseWrapper.compileStatement("PRAGMA quick_check(1)").use { statement -> - val result = statement.simpleQueryForString() - if (result == null || !result.equals("ok", ignoreCase = true)) { - // integrity_checker failed on main or attached databases - FlowLog.log(FlowLog.Level.E, "PRAGMA integrity_check on ${databaseDefinition.databaseName} returned: $result") - integrityOk = false - if (databaseDefinition.backupEnabled()) { - integrityOk = restoreBackUp() - } + public boolean isDatabaseIntegrityOk(DatabaseWrapper databaseWrapper) { + boolean integrityOk = true; + DatabaseStatement statement = databaseWrapper.compileStatement("PRAGMA quick_check(1)"); + String result = statement.simpleQueryForString(); + if (result == null || !result.equalsIgnoreCase("ok")) { + // integrity_checker failed on main or attached databases + FlowLog.log(FlowLog.Level.E, "PRAGMA integrity_check on "+ databaseDefinition.getDatabaseName() +" returned: "+ result); + integrityOk = false; + if (databaseDefinition.backupEnabled()) { + integrityOk = restoreBackUp(); } } - return integrityOk + return integrityOk; } /** * If integrity check fails, this method will use the backup db to fix itself. In order to prevent * loss of data, please backup often! */ - fun restoreBackUp(): Boolean { - var success = true + public boolean restoreBackUp() { + boolean success = true; - val db = context.getDatabasePath(TEMP_DB_NAME + databaseDefinition.databaseName) - val corrupt = context.getDatabasePath(databaseDefinition.databaseName) + File db = DatabaseFileUtils.getDatabasePath(context,TEMP_DB_NAME + databaseDefinition.getDatabaseName()); + File corrupt = DatabaseFileUtils.getDatabasePath(context, databaseDefinition.getDatabaseName()); if (corrupt.delete()) { try { - writeDB(corrupt, FileInputStream(db)) - } catch (e: IOException) { - FlowLog.logError(e) - success = false + writeDB(corrupt, new FileInputStream(db)); + } catch (IOException e) { + FlowLog.logError(e); + success = false; } } else { - FlowLog.log(FlowLog.Level.E, "Failed to delete DB") + FlowLog.log(FlowLog.Level.E, "Failed to delete DB"); } - return success + return success; } /** @@ -220,67 +238,64 @@ class DatabaseHelperDelegate( * * @param dbPath The file to write to. * @param existingDB The existing database file's input stream¬ - * @throws IOException */ - @Throws(IOException::class) - private fun writeDB(dbPath: File, existingDB: InputStream) { - val output = FileOutputStream(dbPath) + private void writeDB(File dbPath, InputStream existingDB) throws IOException { + OutputStream output = new FileOutputStream(dbPath); - val buffer = ByteArray(1024) - var length: Int = existingDB.read(buffer) + byte[] buffer = new byte[1024]; + int length = existingDB.read(buffer); while (length > 0) { - output.write(buffer, 0, length) - length = existingDB.read(buffer) + output.write(buffer, 0, length); + length = existingDB.read(buffer); } - output.flush() - output.close() - existingDB.close() + output.flush(); + output.close(); + existingDB.close(); } /** * Saves the database as a backup on the [DefaultTransactionQueue]. * This will create a THIRD database to use as a backup to the backup in case somehow the overwrite fails. */ - override fun backupDB() { + @Override + public void backupDB() { if (!databaseDefinition.backupEnabled() || !databaseDefinition.areConsistencyChecksEnabled()) { - throw IllegalStateException("Backups are not enabled for : " + - "${databaseDefinition.databaseName}. Please consider adding both backupEnabled " + - "and consistency checks enabled to the Database annotation") + throw new IllegalStateException("Backups are not enabled for : " + + databaseDefinition.getDatabaseName() + ". Please consider adding both backupEnabled " + + "and consistency checks enabled to the Database annotation"); } - databaseDefinition.beginTransactionAsync { - val backup = context.getDatabasePath(tempDbFileName) - val temp = context.getDatabasePath("$TEMP_DB_NAME-2-${databaseDefinition.databaseFileName}") - - // if exists we want to delete it before rename - if (temp.exists()) { - temp.delete() - } + databaseDefinition.beginTransactionAsync(new Function() { + @Override + public Object apply(DatabaseWrapper databaseWrapper) { + File backup = DatabaseFileUtils.getDatabasePath(context, tempDbFileName()); + File temp = DatabaseFileUtils.getDatabasePath(context,TEMP_DB_NAME + "-2-" + databaseDefinition.getDatabaseFileName()); - backup.renameTo(temp) - if (backup.exists()) { - backup.delete() - } - val existing = context.getDatabasePath(databaseDefinition.databaseFileName) + // if exists we want to delete it before rename + if (temp.exists()) { + temp.delete(); + } - try { - backup.parentFile.mkdirs() - writeDB(backup, FileInputStream(existing)) - temp.delete() - } catch (e: Exception) { - FlowLog.logError(e) + backup.renameTo(temp); + if (backup.exists()) { + backup.delete(); + } + File existing = DatabaseFileUtils.getDatabasePath(context, databaseDefinition.getDatabaseFileName()); + try { + backup.getParentFile().mkdirs(); + writeDB(backup, new FileInputStream(existing)); + temp.delete(); + } catch (Exception e) { + FlowLog.logError(e); + } + return null; } - }.execute() - + }).execute(null, null, null, null); } - companion object { - - val TEMP_DB_NAME = "temp-" - - fun getTempDbFileName(databaseDefinition: DBFlowDatabase): String = - "$TEMP_DB_NAME${databaseDefinition.databaseName}.db" + public static String getTempDbFileName(DBFlowDatabase databaseDefinition) { + return TEMP_DB_NAME + databaseDefinition.getDatabaseName() + ".db"; } } diff --git a/lib/src/main/java/com/dbflow5/database/MigrationFileHelper.java b/lib/src/main/java/com/dbflow5/database/MigrationFileHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..8db49c5486ab4ed7e70dc5de21d7c0c4d3f1b3af --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/MigrationFileHelper.java @@ -0,0 +1,14 @@ +package com.dbflow5.database; + +import java.util.List; +import java.util.function.Function; + +/** + * Description: + */ +public interface MigrationFileHelper { + + List getListFiles(String dbMigrationPath); + + void executeMigration(String fileName, Function dbFunction); +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/database/OhosDatabase.java b/lib/src/main/java/com/dbflow5/database/OhosDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..a2edd41dd34ed20fc272e1936ecb20a40e964f48 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OhosDatabase.java @@ -0,0 +1,122 @@ +package com.dbflow5.database; + +import ohos.data.rdb.RdbPredicates; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.ValuesBucket; + +import java.util.function.Function; + +/** + * Description: Specifies the android default implementation of a database. + */ +public class OhosDatabase implements OhosDatabaseWrapper { + RdbStore database; + + public OhosDatabase(RdbStore database){ + this.database = database; + } + + @Override + public void execSQL(String query) { + rethrowDBFlowException(unused -> { + database.executeSql(query); + return null; + }); + } + + @Override + public boolean isInTransaction(){ + return database.isInTransaction(); + } + + @Override + public void beginTransaction() { + database.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + //database.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + database.endTransaction(); + } + + @Override + public int getVersion() { + return database.getVersion(); + } + + @Override + public DatabaseStatement compileStatement(String rawQuery) { + System.out.println("rawQuery:"+rawQuery); + return rethrowDBFlowException (unused -> OhosDatabaseStatement.from(rawQuery, database.buildStatement(rawQuery), database)); + } + + @Override + public FlowCursor rawQuery(String query, String[] selectionArgs) { + return FlowCursor.from(database.querySql(query, selectionArgs)); + } + + @Override + public long updateWithOnConflict(String tableName, ValuesBucket contentValues, String[] whereColumn, String[] whereValues, RdbStore.ConflictResolution conflictAlgorithm) { + RdbPredicates rdbPredicates = new RdbPredicates(tableName); + if(whereColumn != null && whereValues != null){ + for (int index = 0;index < whereColumn.length; index++){ + rdbPredicates.equalTo(whereColumn[index], whereValues[index]); + } + } + + return rethrowDBFlowException(unused -> (long)database.updateWithConflictResolution(contentValues, rdbPredicates, conflictAlgorithm)); + } + + @Override + public long insertWithOnConflict(String tableName, String nullColumnHack, ValuesBucket values, RdbStore.ConflictResolution sqLiteDatabaseAlgorithmInt) { + return rethrowDBFlowException(unused -> database.insertWithConflictResolution(tableName, values, sqLiteDatabaseAlgorithmInt)); + } + + @Override + public FlowCursor query(String tableName, String[] columns, String[] selection, String[] selectionArgs, String groupBy, String having, String orderBy){ + RdbPredicates rdbPredicates = new RdbPredicates(tableName); + + if(selection != null && selectionArgs != null){ + for (int index = 0; index < selection.length; index++){ + rdbPredicates.equalTo(selection[index], selectionArgs[index]); + } + } + rdbPredicates.orderByDesc(orderBy); + rdbPredicates.groupBy(new String[]{groupBy}); + + return rethrowDBFlowException(unused -> FlowCursor.from(database.query(rdbPredicates, columns))); + } + + @Override + public int delete(String tableName, String[] whereClause, String[] whereArgs) { + RdbPredicates rdbPredicates = new RdbPredicates(tableName); + if(whereClause != null && whereArgs != null){ + for (int index = 0; index < whereClause.length; index++){ + rdbPredicates.equalTo(whereClause[index], whereArgs[index]); + } + } + return rethrowDBFlowException (unused -> database.delete(rdbPredicates)); + } + + public static OhosDatabase from(RdbStore database) { + return new OhosDatabase(database); + } + + public static SQLiteException toDBFlowSQLiteException(SQLiteException e) { + return new SQLiteException("A Database Error Occurred", e); + } + + public static T rethrowDBFlowException(Function fn) { + try { + return fn.apply(null); + } catch (SQLiteException e) { + throw toDBFlowSQLiteException(e); + } + } +} + diff --git a/lib/src/main/java/com/dbflow5/database/OhosDatabaseStatement.java b/lib/src/main/java/com/dbflow5/database/OhosDatabaseStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..7b41af4ff513b32269571844fdc2330b1191b335 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OhosDatabaseStatement.java @@ -0,0 +1,85 @@ +package com.dbflow5.database; + + +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.Statement; + +/** + * Description: + */ +public class OhosDatabaseStatement extends BaseDatabaseStatement implements DatabaseStatement { + + Statement statement; + private final RdbStore database; + private final String sql; + + public OhosDatabaseStatement(String sql, Statement statement, RdbStore database){ + this.sql = sql; + this.statement = statement; + this.database = database; + } + + @Override + public long executeUpdateDelete() { + return statement.executeAndGetChanges(); + } + + @Override + public void execute() { + statement.execute(); + } + + @Override + public void close() { + statement.close(); + } + + @Override + public long simpleQueryForLong() { + return OhosDatabase.rethrowDBFlowException(unused -> statement.executeAndGetLong()); + } + + @Override + public String simpleQueryForString() { + return OhosDatabase.rethrowDBFlowException(unused -> statement.executeAndGetString()); + } + + @Override + public long executeInsert() { + return OhosDatabase.rethrowDBFlowException(unused -> statement.executeAndGetLastInsertRowId()); + } + + @Override + public void bindString(int index, String s) { + statement.setString(index, s); + } + + @Override + public void bindNull(int index) { + statement.setNull(index); + } + + @Override + public void bindLong(int index, Long aLong) { + statement.setLong(index, aLong); + } + + @Override + public void bindDouble(int index, Double aDouble) { + statement.setDouble(index, aDouble); + } + + @Override + public void bindBlob(int index, byte[] bytes) { + statement.setBlob(index, bytes); + } + + @Override + public void bindAllArgsAsStrings(String[] selectionArgs) { + statement.setStrings(selectionArgs); + } + + public static OhosDatabaseStatement from(String sql, Statement sqLiteStatement, RdbStore database) { + return new OhosDatabaseStatement(sql, sqLiteStatement, database); + } +} diff --git a/lib/src/main/java/com/dbflow5/database/OhosDatabaseWrapper.java b/lib/src/main/java/com/dbflow5/database/OhosDatabaseWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..1a4c74c758851bad770c882a9806c22f2165af70 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OhosDatabaseWrapper.java @@ -0,0 +1,22 @@ +package com.dbflow5.database; + +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.ValuesBucket; + +/** + * Description: + */ +public interface OhosDatabaseWrapper extends DatabaseWrapper { + + + long updateWithOnConflict(String tableName, + ValuesBucket contentValues, + String[] whereColumn, + String[] whereValues, RdbStore.ConflictResolution conflictAlgorithm); + + long insertWithOnConflict(String tableName, + String nullColumnHack, + ValuesBucket values, + RdbStore.ConflictResolution sqLiteDatabaseAlgorithmInt); + +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/database/OhosMigrationFileHelper.java b/lib/src/main/java/com/dbflow5/database/OhosMigrationFileHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..9e17538ec301b4226cfbd237a2f1459c2095df3f --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OhosMigrationFileHelper.java @@ -0,0 +1,71 @@ +package com.dbflow5.database; + +import com.dbflow5.StringUtils; +import com.dbflow5.config.FlowLog; +import ohos.app.Context; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Description: Implements [MigrationFileHelper] for Android targets. + */ +public class OhosMigrationFileHelper implements MigrationFileHelper { + private final Context context; + + public OhosMigrationFileHelper(Context context){ + this.context = context; + } + + @Override + public List getListFiles(String dbMigrationPath) { + String[] strArray = context.getDatabaseDir().list(); + if(strArray == null){ + strArray = new String[]{}; + } + return Arrays.asList(strArray); + } + + @Override + public void executeMigration(String fileName, Function dbFunction) { + try { + InputStream input = new FileInputStream(fileName); + + // ends line with SQL + String querySuffix = ";"; + + // standard java comments + String queryCommentPrefix = "--"; + StringBuffer query = new StringBuffer(); + + InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8); + BufferedReader bufferedReader = new BufferedReader(reader, 8*1024); + String fileLine; + while((fileLine = bufferedReader.readLine()) != null){ + String line = fileLine.trim(); + boolean isEndOfQuery = line.endsWith(querySuffix); + if (line.startsWith(queryCommentPrefix)) { + return; + } + if (isEndOfQuery) { + line = line.substring(0, line.length() - querySuffix.length()); + } + query.append(" ").append(line); + if (isEndOfQuery) { + dbFunction.apply(query.toString()); + query = new StringBuffer(); + } + } + + String queryString = query.toString(); + if (StringUtils.isNotNullOrEmpty(queryString.trim())) { + dbFunction.apply(queryString); + } + } catch (IOException e) { + FlowLog.log(FlowLog.Level.E, "Failed to execute "+fileName+". App might be in an inconsistent state!", e); + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/database/OhosSQLiteOpenHelper.java b/lib/src/main/java/com/dbflow5/database/OhosSQLiteOpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..910a38f53f7dd59395fceab6d8fe63bd802efcde --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OhosSQLiteOpenHelper.java @@ -0,0 +1,205 @@ +package com.dbflow5.database; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseConfig; +import ohos.app.Context; +import ohos.data.DatabaseHelper; +import ohos.data.rdb.RdbOpenCallback; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.StoreConfig; + +/** + * Description: Wraps around the [SQLiteOpenHelper] and provides extra features for use in this library. + */ +public class OhosSQLiteOpenHelper extends DatabaseHelper implements OpenHelper, OpenHelperDelegate /*by databaseHelperDelegate*/ { + + DBFlowDatabase dbFlowDatabase; + DatabaseCallback listener; + private final LocalDatabaseHelperDelegate databaseHelperDelegate; + + private OhosDatabase androidDatabase = null; + private final String _databaseName; + + public OhosSQLiteOpenHelper(Context context, DBFlowDatabase dbFlowDatabase, DatabaseCallback listener) { + super(context); + this.dbFlowDatabase = dbFlowDatabase; + this.listener = listener; + _databaseName = dbFlowDatabase.getDatabaseFileName(); + + OpenHelper openHelper = null; + if(dbFlowDatabase.backupEnabled()){ + // Temp database mirrors existing + openHelper = new BackupHelper(context, LocalDatabaseHelperDelegate.getTempDbFileName(dbFlowDatabase), dbFlowDatabase.databaseVersion(), dbFlowDatabase); + } + databaseHelperDelegate = new LocalDatabaseHelperDelegate(context, listener, dbFlowDatabase, openHelper); + } + + @Override + public DatabaseWrapper database(){ + if (androidDatabase == null || !androidDatabase.database.isOpen()) { + StoreConfig config = StoreConfig.newDefaultConfig(dbFlowDatabase.getDatabaseFileName()); + RdbOpenCallback callback = new RdbOpenCallback() { + @Override + public void onCreate(RdbStore store) { + databaseHelperDelegate.onCreate(OhosDatabase.from(store)); + } + + @Override + public void onOpen(RdbStore store) { + databaseHelperDelegate.onOpen(OhosDatabase.from(store)); + } + + @Override + public void onUpgrade(RdbStore store, int oldVersion, int newVersion) { + databaseHelperDelegate.onUpgrade(OhosDatabase.from(store), oldVersion, newVersion); + } + + @Override + public void onDowngrade(RdbStore store, int currentVersion, int targetVersion) { + databaseHelperDelegate.onDowngrade(OhosDatabase.from(store), currentVersion, targetVersion); + } + }; + androidDatabase = OhosDatabase.from(getRdbStore(config, dbFlowDatabase.databaseVersion(), callback)); + } + return androidDatabase; + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + } + + @Override + public void setDatabaseListener(DatabaseCallback callback) { + databaseHelperDelegate.setDatabaseHelperListener(callback); + } + + @Override + public void closeDB() { + if(androidDatabase != null && androidDatabase.database != null) { + androidDatabase.database.close(); + } + } + + @Override + public void deleteDB() { + deleteRdbStore(_databaseName); + } + + @Override + public LocalDatabaseHelperDelegate delegate() { + return null; + } + + @Override + public boolean isDatabaseIntegrityOk() { + return false; + } + + @Override + public void performRestoreFromBackup() { + databaseHelperDelegate.performRestoreFromBackup(); + } + + @Override + public void backupDB() { + databaseHelperDelegate.backupDB(); + } + + /** + * Simple helper to manage backup. + */ + private static class BackupHelper extends DatabaseHelper implements OpenHelper { + //context, name, null, version + Context context; + String name; + int version; + DBFlowDatabase databaseDefinition; + + private OhosDatabase androidDatabase = null; + private final LocalDatabaseHelper databaseHelper; + private final String _databaseName; + + BackupHelper(Context context, String name, int version, DBFlowDatabase databaseDefinition){ + super(context); + this.context = context; + this.name = name; + this.version = version; + this.databaseDefinition = databaseDefinition; + this.databaseHelper = new LocalDatabaseHelper(new OhosMigrationFileHelper(context), databaseDefinition); + this._databaseName = databaseDefinition.getDatabaseFileName(); + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + } + + @Override + public void setDatabaseListener(DatabaseCallback callback) { + } + + @Override + public void closeDB() { + + } + + @Override + public void deleteDB() { + deleteRdbStore(_databaseName); + } + + @Override + public DatabaseWrapper database() { + if (androidDatabase == null) { + StoreConfig config = StoreConfig.newDefaultConfig(name); + RdbOpenCallback callback = new RdbOpenCallback() { + @Override + public void onCreate(RdbStore store) { + databaseHelper.onCreate(OhosDatabase.from(store)); + } + + @Override + public void onOpen(RdbStore store) { + databaseHelper.onOpen(OhosDatabase.from(store)); + } + + @Override + public void onUpgrade(RdbStore store, int oldVersion, int newVersion) { + databaseHelper.onUpgrade(OhosDatabase.from(store), oldVersion, newVersion); + } + + @Override + public void onDowngrade(RdbStore store, int currentVersion, int targetVersion) { + databaseHelper.onDowngrade(OhosDatabase.from(store), currentVersion, targetVersion); + } + }; + androidDatabase = OhosDatabase.from(getRdbStore(config, version, callback)); + } + return androidDatabase; + } + + @Override + public LocalDatabaseHelperDelegate delegate() { + return null; + } + + @Override + public boolean isDatabaseIntegrityOk() { + return false; + } + + @Override + public void performRestoreFromBackup() { + } + + @Override + public void backupDB() { + + } + } + + public static DatabaseConfig.OpenHelperCreator createHelperCreator(Context context) { + return (db, callback) -> new OhosSQLiteOpenHelper(context, db, callback); + } + +} + diff --git a/lib/src/main/java/com/dbflow5/database/OpenHelper.java b/lib/src/main/java/com/dbflow5/database/OpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..52e145bda30f2dffc55d61048aa4a0708b745fd2 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OpenHelper.java @@ -0,0 +1,15 @@ +package com.dbflow5.database; + +/** + * Description: Abstracts out the [DatabaseHelperDelegate] into the one used in this library. + */ +public interface OpenHelper extends OpenHelperDelegate { + + void setWriteAheadLoggingEnabled(boolean enabled); + + void setDatabaseListener(DatabaseCallback callback); + + void closeDB(); + + void deleteDB(); +} diff --git a/lib/src/main/java/com/dbflow5/database/OpenHelperDelegate.java b/lib/src/main/java/com/dbflow5/database/OpenHelperDelegate.java new file mode 100644 index 0000000000000000000000000000000000000000..dd6584e6ce5d554c964ed2e78538b32f67086eac --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/OpenHelperDelegate.java @@ -0,0 +1,13 @@ +package com.dbflow5.database; + +public interface OpenHelperDelegate { + DatabaseWrapper database(); + + LocalDatabaseHelperDelegate delegate(); + + boolean isDatabaseIntegrityOk(); + + void performRestoreFromBackup(); + + void backupDB(); +} diff --git a/lib/src/main/java/com/dbflow5/database/SQLiteException.java b/lib/src/main/java/com/dbflow5/database/SQLiteException.java new file mode 100644 index 0000000000000000000000000000000000000000..53496b604d03db438504cf6ef3e22c2626f04f7a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/database/SQLiteException.java @@ -0,0 +1,18 @@ +package com.dbflow5.database; + +/** + * Description: DBFlow mirror to an Android SQLiteException. + */ +public class SQLiteException extends RuntimeException { + public SQLiteException() { + } + + public SQLiteException(String message) { + super(message); + } + + public SQLiteException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/lib/src/main/kotlin/com/dbflow5/migration/AlterTableMigration.kt b/lib/src/main/java/com/dbflow5/migration/AlterTableMigration.java similarity index 39% rename from lib/src/main/kotlin/com/dbflow5/migration/AlterTableMigration.kt rename to lib/src/main/java/com/dbflow5/migration/AlterTableMigration.java index a9f97f4a299c6c886f2ce1a41ad1d28b20fb5b22..5926b2f5c95dc17d5464c146724e890c79d2690f 100644 --- a/lib/src/main/kotlin/com/dbflow5/migration/AlterTableMigration.kt +++ b/lib/src/main/java/com/dbflow5/migration/AlterTableMigration.java @@ -1,84 +1,80 @@ -package com.dbflow5.migration - -import androidx.annotation.CallSuper -import com.dbflow5.appendQuotedIfNeeded -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.select -import com.dbflow5.quoteIfNeeded -import com.dbflow5.sql.SQLiteType -import com.dbflow5.stripQuotes -import java.util.* -import kotlin.reflect.KClass +package com.dbflow5.migration; + +import com.dbflow5.StringUtils; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.Select; +import com.dbflow5.sql.SQLiteType; +import java.util.*; + +import com.dbflow5.annotation.CallSuper; /** * Description: Provides a very nice way to alter a single table quickly and easily. */ -open class AlterTableMigration( - /** - * The table to ALTER - */ - private val table: Class) : BaseMigration() { - +public class AlterTableMigration extends BaseMigration { + private static final String ALTER_TABLE = "ALTER TABLE "; - constructor(table: KClass) : this(table.java) + private final Class table; /** * The query to rename the table with */ - private var renameQuery: String? = null + private String renameQuery = null; /** * The columns to ALTER within a table */ - private val internalColumnDefinitions: MutableList by lazy { mutableListOf() } + private final List internalColumnDefinitions = new ArrayList<>(); - private val columnNames: MutableList by lazy { arrayListOf() } + private final List columnNames = new ArrayList<>(); /** * The old name of the table before renaming it */ - private var oldTableName: String? = null + private String oldTableName = null; - override fun migrate(database: DatabaseWrapper) { + public AlterTableMigration(Class table){ + this.table = table; + } + + @Override + public void migrate(DatabaseWrapper database) { // "ALTER TABLE " - var sql = ALTER_TABLE - val tableName = FlowManager.getTableName(table) + String sql = ALTER_TABLE; + String tableName = FlowManager.getTableName(table); // "{oldName} RENAME TO {newName}" // Since the structure has been updated already, the manager knows only the new name. - renameQuery?.let { renameQuery -> - database.execSQL(buildString { - append(sql) - appendQuotedIfNeeded(oldTableName) - append(renameQuery) - append(tableName) - }) - } + StringBuilder buildString = new StringBuilder(); + buildString.append(sql); + StringUtils.appendQuotedIfNeeded(buildString, oldTableName); + buildString.append(renameQuery); + buildString.append(tableName); + database.execSQL(buildString.toString()); // We have column definitions to add here // ADD COLUMN columnName {type} - if (internalColumnDefinitions.isNotEmpty()) { - (select from table.kotlin limit 0).cursor(database)?.use { cursor -> - sql = "$sql$tableName" - for (i in internalColumnDefinitions.indices) { - val columnDefinition = internalColumnDefinitions[i] - val columnName = columnNames[i].stripQuotes() - if (cursor.getColumnIndex(columnName) == -1) { - database.execSQL("$sql ADD COLUMN $columnDefinition") - } + if (!internalColumnDefinitions.isEmpty()) { + FlowCursor cursor = Select.select().from(table).limit(0).cursor(database); + sql = sql + tableName; + for (int index = 0; index < internalColumnDefinitions.size(); index++) { + String columnDefinition = internalColumnDefinitions.get(index); + String columnName = StringUtils.stripQuotes(columnNames.get(index)); + if (cursor.getColumnIndexForName(columnName) == -1) { + database.execSQL(sql+" ADD COLUMN "+columnDefinition); } } } } @CallSuper - override fun onPostMigrate() { + public void onPostMigrate() { // cleanup and make fields eligible for garbage collection - renameQuery = null - internalColumnDefinitions.clear() - columnNames.clear() + renameQuery = null; + internalColumnDefinitions.clear(); + columnNames.clear(); } /** @@ -87,9 +83,10 @@ open class AlterTableMigration( * @param oldName The new name to call the table. * @return This instance */ - fun renameFrom(oldName: String) = apply { - oldTableName = oldName - renameQuery = " RENAME TO " + public AlterTableMigration renameFrom(String oldName) { + oldTableName = oldName; + renameQuery = " RENAME TO "; + return this; } /** @@ -103,14 +100,14 @@ open class AlterTableMigration( * For NULL column add defaultValue = "NULL". Encapsulate the value in quotes "\'name\'" if it's a string. * @return This instance */ - @JvmOverloads - fun addColumn(sqLiteType: SQLiteType, columnName: String, defaultValue: String? = null) = apply { - var addStatement = "${columnName.quoteIfNeeded()} ${sqLiteType.name}" + public AlterTableMigration addColumn(SQLiteType sqLiteType, String columnName, String defaultValue) { + String addStatement = StringUtils.quoteIfNeeded(columnName) + " " + sqLiteType.name(); if (defaultValue != null) { - addStatement += " DEFAULT $defaultValue" + addStatement += " DEFAULT " + defaultValue; } - internalColumnDefinitions.add(addStatement) - columnNames.add(columnName) + internalColumnDefinitions.add(addStatement); + columnNames.add(columnName); + return this; } /** @@ -122,31 +119,35 @@ open class AlterTableMigration( * @param referenceClause The clause of the references that this foreign key points to. * @return This instance */ - fun addForeignKeyColumn(sqLiteType: SQLiteType, columnName: String, referenceClause: String) = apply { - internalColumnDefinitions.add("${columnName.quoteIfNeeded()} ${sqLiteType.name} REFERENCES $referenceClause") - columnNames.add(columnName) + public AlterTableMigration addForeignKeyColumn(SQLiteType sqLiteType, String columnName, String referenceClause) { + internalColumnDefinitions.add("${columnName.quoteIfNeeded()} ${sqLiteType.name} REFERENCES $referenceClause"); + columnNames.add(columnName); + return this; } /** * @return The query that renames the table. */ - fun getRenameQuery(): String = buildString { - append(ALTER_TABLE) - appendQuotedIfNeeded(oldTableName) - append(renameQuery) - append(FlowManager.getTableName(table)) + public String getRenameQuery() { + StringBuilder builder = new StringBuilder(); + builder.append(ALTER_TABLE); + StringUtils.appendQuotedIfNeeded(builder, oldTableName); + builder.append(renameQuery); + builder.append(FlowManager.getTableName(table)); + return builder.toString(); } /** * @return A List of column definitions that add op to a table in the DB. */ - fun getColumnDefinitions(): List { - val sql = "$ALTER_TABLE ${FlowManager.getTableName(table)}" - return internalColumnDefinitions.map { "$sql ADD COLUMN $it" } + public List getColumnDefinitions() { + String sql = ALTER_TABLE + " " + FlowManager.getTableName(table); + List newListString = new ArrayList<>(); + List listString = internalColumnDefinitions; + for(String columns : listString){ + listString.add(sql + " ADD COLUMN " + columns); + } + return newListString; } - companion object { - - const val ALTER_TABLE = "ALTER TABLE " - } } diff --git a/lib/src/main/java/com/dbflow5/migration/BaseMigration.java b/lib/src/main/java/com/dbflow5/migration/BaseMigration.java new file mode 100644 index 0000000000000000000000000000000000000000..157a64c1cce344ec1f9d3c240e90f21d89c146a2 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/migration/BaseMigration.java @@ -0,0 +1,25 @@ +package com.dbflow5.migration; + +import com.dbflow5.database.DatabaseWrapper; +import ohos.aafwk.ability.DataAbilityRemoteException; + +/** + * Description: Provides the base implementation of [Migration] with + * only [Migration.migrate] needing to be implemented. + */ +public abstract class BaseMigration implements Migration { + + @Override + public void onPreMigrate() { + + } + + @Override + public abstract void migrate(DatabaseWrapper database) throws DataAbilityRemoteException; + + @Override + public void onPostMigrate() { + + } + +} diff --git a/lib/src/main/java/com/dbflow5/migration/IndexMigration.java b/lib/src/main/java/com/dbflow5/migration/IndexMigration.java new file mode 100644 index 0000000000000000000000000000000000000000..c76b8424ab90003e9a75a0d88777eaee2a2bf01c --- /dev/null +++ b/lib/src/main/java/com/dbflow5/migration/IndexMigration.java @@ -0,0 +1,52 @@ +package com.dbflow5.migration; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.Index; +import com.dbflow5.query.SQLite; +import com.dbflow5.query.property.IProperty; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: Defines and enables an Index structurally through a migration. + */ +public abstract class IndexMigration extends BaseMigration { + private Class onTable; + + private boolean unique = false; + private final List> columns = new ArrayList>(); + + abstract String name(); + + @Override + public void migrate(DatabaseWrapper database) { + Index index = SQLite.index(name(), onTable).unique(unique); + for(IProperty property:columns){ + index.and(property); + } + database.execSQL(index.getQuery()); + } + + /** + * Adds a column to the underlying INDEX + * + * @param property The name of the column to add to the Index + * @return This migration + */ + public IndexMigration addColumn(IProperty property) { + columns.add(property); + return this; + } + + /** + * Sets the INDEX to UNIQUE + * + * @return This migration. + */ + public IndexMigration unique() { + unique = true; + return this; + } + +} diff --git a/lib/src/main/java/com/dbflow5/migration/IndexPropertyMigration.java b/lib/src/main/java/com/dbflow5/migration/IndexPropertyMigration.java new file mode 100644 index 0000000000000000000000000000000000000000..35f8447d77d07946aa0cf19a573514d0e0a3129e --- /dev/null +++ b/lib/src/main/java/com/dbflow5/migration/IndexPropertyMigration.java @@ -0,0 +1,22 @@ +package com.dbflow5.migration; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.property.IndexProperty; + +/** + * Description: Allows you to specify if and when an [IndexProperty] gets used or created. + */ +public abstract class IndexPropertyMigration extends BaseMigration { + protected boolean shouldCreate = true; + + abstract IndexProperty indexProperty(); + + @Override + public void migrate(DatabaseWrapper database) { + if (shouldCreate) { + indexProperty().createIfNotExists(database); + } else { + indexProperty().drop(database); + } + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/migration/Migration.kt b/lib/src/main/java/com/dbflow5/migration/Migration.java similarity index 66% rename from lib/src/main/kotlin/com/dbflow5/migration/Migration.kt rename to lib/src/main/java/com/dbflow5/migration/Migration.java index 6e2a99d865de826eb4847df12480eb1c40080da1..68b99c6d0d4d2b95d65921ad7f271e282078b947 100644 --- a/lib/src/main/kotlin/com/dbflow5/migration/Migration.kt +++ b/lib/src/main/java/com/dbflow5/migration/Migration.java @@ -1,27 +1,28 @@ -package com.dbflow5.migration +package com.dbflow5.migration; -import com.dbflow5.database.DatabaseWrapper +import com.dbflow5.database.DatabaseWrapper; +import ohos.aafwk.ability.DataAbilityRemoteException; /** * Description: Called when the Database is migrating. We can perform custom migrations here. A [com.dbflow5.annotation.Migration] * is required for registering this class to automatically be called in an upgrade of the DB. */ -interface Migration { +public interface Migration { /** * Called before we migrate data. Instantiate migration data before releasing it in [.onPostMigrate] */ - fun onPreMigrate() + void onPreMigrate(); /** * Perform your migrations here * * @param database The database to operate on */ - fun migrate(database: DatabaseWrapper) + void migrate(DatabaseWrapper database) throws DataAbilityRemoteException; /** * Called after the migration completes. Release migration data here. */ - fun onPostMigrate() + void onPostMigrate(); } diff --git a/lib/src/main/kotlin/com/dbflow5/migration/UpdateTableMigration.kt b/lib/src/main/java/com/dbflow5/migration/UpdateTableMigration.java similarity index 34% rename from lib/src/main/kotlin/com/dbflow5/migration/UpdateTableMigration.kt rename to lib/src/main/java/com/dbflow5/migration/UpdateTableMigration.java index a2b7bb38219314b202475dcf6d050e8acdbc979a..8e7b9cf3953c384cee1832d8a5dcde5166a7e38f 100644 --- a/lib/src/main/kotlin/com/dbflow5/migration/UpdateTableMigration.kt +++ b/lib/src/main/java/com/dbflow5/migration/UpdateTableMigration.java @@ -1,45 +1,44 @@ -package com.dbflow5.migration +package com.dbflow5.migration; -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.BaseQueriable -import com.dbflow5.query.OperatorGroup -import com.dbflow5.query.SQLOperator -import com.dbflow5.query.update -import kotlin.reflect.KClass +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.BaseQueriable; +import com.dbflow5.query.OperatorGroup; +import com.dbflow5.query.SQLOperator; +import com.dbflow5.query.SQLite; + +import java.util.Arrays; /** * Description: Provides a simple way to update a table's field or fields quickly in a migration. * It ties an SQLite [com.dbflow5.sql.language.Update] * to migrations whenever we want to batch update tables in a structured manner. */ -open class UpdateTableMigration -/** - * Creates an update migration. - * - * @param table The table to update - */ -( - /** - * The table to update - */ - private val table: Class) : BaseMigration() { +public class UpdateTableMigration extends BaseMigration { + private Class table; - constructor(table: KClass) : this(table.java) + public UpdateTableMigration(Class table){ + this.table = table; + } /** * Builds the conditions for the WHERE part of our query */ - private val whereOperatorGroup: OperatorGroup by lazy { OperatorGroup.nonGroupingClause() } + private OperatorGroup whereOperatorGroup() { + return OperatorGroup.nonGroupingClause(); + } /** * The conditions to use to set fields in the update query */ - private val setOperatorGroup: OperatorGroup by lazy { OperatorGroup.nonGroupingClause() } + private OperatorGroup setOperatorGroup(){ + return OperatorGroup.nonGroupingClause(); + } - val updateStatement: BaseQueriable - get() = update(table) - .set(setOperatorGroup) - .where(whereOperatorGroup) + public BaseQueriable updateStatement(){ + return SQLite.update(table) + .set(setOperatorGroup()) + .where(whereOperatorGroup()); + } /** * This will append a condition to this migration. It will execute each of these in succession with the order @@ -47,15 +46,18 @@ open class UpdateTableMigration * * @param conditions The conditions to append */ - fun set(vararg conditions: SQLOperator) = apply { - setOperatorGroup.andAll(*conditions) + public UpdateTableMigration set(SQLOperator... conditions) { + setOperatorGroup().andAll(Arrays.asList(conditions)); + return this; } - fun where(vararg conditions: SQLOperator) = apply { - whereOperatorGroup.andAll(*conditions) + public UpdateTableMigration where(SQLOperator... conditions) { + whereOperatorGroup().andAll(Arrays.asList(conditions)); + return this; } - override fun migrate(database: DatabaseWrapper) { - updateStatement.execute(database) + @Override + public void migrate(DatabaseWrapper database) { + updateStatement().execute(database); } } diff --git a/lib/src/main/java/com/dbflow5/observing/ObservingTableTracker.java b/lib/src/main/java/com/dbflow5/observing/ObservingTableTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..895f44876d865a7e0d07cd22628fc3178e2009c8 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/observing/ObservingTableTracker.java @@ -0,0 +1,86 @@ +package com.dbflow5.observing; + +import java.util.Arrays; + +/** + * Description: Keeps track of how many observers are registered on a [TableObserver] at a given time. + */ +public class ObservingTableTracker{ + + private int tableCount; + + public ObservingTableTracker(int tableCount){ + this.tableCount = tableCount; + Arrays.fill(triggerStateChanges, Operation.None); + } + + enum Operation { + None, + Add, + Remove + } + + private Long[] observerPerTable = new Long[tableCount]; + private Boolean[] previousTriggerStates = new Boolean[tableCount]; + + // caches the changes in a fixed array. + private Operation[] triggerStateChanges = new Operation[tableCount]; + + private boolean needsSync = false; + private boolean pendingSync = false; + + + boolean onAdded(int[] tableIds){ + return adjustObserverCount(tableIds, 1L, 0L); + } + + boolean onRemoved(int[] tableIds){ + return adjustObserverCount(tableIds, -1L, 1L); + } + + private boolean adjustObserverCount(int[] tableIds, long value, long countToSync) { + boolean syncTriggers = false; + synchronized(this) { + for(int tableId : tableIds){ + Long count = observerPerTable[tableId]; + observerPerTable[tableId] = count + value; + if (count == countToSync) { + syncTriggers = true; + needsSync = true; + } + } + } + return syncTriggers; + } + + void syncCompleted() { + synchronized(this) { + pendingSync = false; + } + } + + public Operation[] tablesToSync(){ + if (!needsSync || pendingSync) { + return null; + } + + for (int index=0;index 0; + if (hasObservers != previousTriggerStates[index]) { + if (hasObservers) { + triggerStateChanges[index] = Operation.Add; + } else { + triggerStateChanges[index] = Operation.None; + } + } else { + triggerStateChanges[index] = Operation.None; + } + previousTriggerStates[index] = hasObservers; + } + + pendingSync = true; + needsSync = false; + return triggerStateChanges; + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/observing/OnTableChangedObserver.java b/lib/src/main/java/com/dbflow5/observing/OnTableChangedObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..5e16a3607c8bf998edd50181bd90089cb7642beb --- /dev/null +++ b/lib/src/main/java/com/dbflow5/observing/OnTableChangedObserver.java @@ -0,0 +1,63 @@ +package com.dbflow5.observing; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Description: + */ +public abstract class OnTableChangedObserver { + + List> tables; + + public OnTableChangedObserver(List> tables){ + this.tables = tables; + } + + /** + * Called when a table or set of tables are invalidated in the DB. + */ + protected abstract void onChanged(Set> tables); + + static class OnTableChangedObserverWithIds{ + + OnTableChangedObserver observer; + int[] tableIds; + + OnTableChangedObserverWithIds(OnTableChangedObserver observer, int[] tableIds){ + this.observer = observer; + this.tableIds = tableIds; + } + + private Set> singleTableSet(){ + if(tableIds.length == 1){ + Set> classSet = new HashSet<>(); + classSet.add(observer.tables.get(0)); + return classSet; + } + return null; + } + + void notifyTables(boolean[] invalidationStatus) { + Set> invalidatedTables = null; + + for(int index= 0;index> singleTableSet = singleTableSet(); + if (singleTableSet != null) { + invalidatedTables = singleTableSet; + } else { + if (invalidatedTables == null) { + invalidatedTables = new HashSet<>(); + } + invalidatedTables.add(observer.tables.get(index)); + } + } + } + observer.onChanged(invalidatedTables); + } + } +} + diff --git a/lib/src/main/java/com/dbflow5/observing/TableObserver.java b/lib/src/main/java/com/dbflow5/observing/TableObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..d94fd6dc39ad00a9f5eb42bc9b72da159db97e0a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/observing/TableObserver.java @@ -0,0 +1,291 @@ +package com.dbflow5.observing; + +import com.dbflow5.StringUtils; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.database.SQLiteException; +import com.dbflow5.query.TriggerMethod; +import com.dbflow5.transaction.Transaction; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +/** + * Description: Tracks table changes in the DB via Triggers. This more efficient than utilizing + * in the app space. + */ +public class TableObserver { + + private static final String TABLE_OBSERVER_NAME = "dbflow_table_log"; + private static final String TRIGGER_PREFIX = "dbflow_table_trigger"; + private static final String INVALIDATED_COLUMN_NAME = "invalidated"; + private static final String TABLE_ID_COLUMN_NAME = "table_id"; + private static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + TABLE_OBSERVER_NAME + " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;"; + + private final DBFlowDatabase db; + private List> tables; + + public TableObserver(DBFlowDatabase db, List> tables) { + this.db = db; + this.tables = tables; + init(); + + observingTableTracker = new ObservingTableTracker(tables.size()); + tableStatus = new boolean[tables.size()]; + } + + private final Map, Integer> tableReferenceMap = new HashMap<>(); + private final Map> tableIndexToNameMap = new HashMap<>(); + + private final ObservingTableTracker observingTableTracker; + private final Map observerToObserverWithIdsMap = new HashMap<>(); + + private final boolean[] tableStatus; + + private boolean initialized = false; + + private final AtomicBoolean pendingRefresh = new AtomicBoolean(false); + + private DatabaseStatement cleanupStatement() { + return db.compileStatement("UPDATE " + TABLE_OBSERVER_NAME + " SET " + INVALIDATED_COLUMN_NAME + " = 0 WHERE " + INVALIDATED_COLUMN_NAME + " = 1;"); + } + + private void init() { + for (int index = 0; index < tables.size(); index++) { + Class name = tables.get(index); + tableReferenceMap.put(name, index); + tableIndexToNameMap.put(index, name); + } + } + + public void addOnTableChangedObserver(OnTableChangedObserver observer) { + int[] newTableIds = new int[observer.tables.size()]; + + for (int index = 0; index <= observer.tables.size(); index++) { + Class table = observer.tables.get(index); + int id = tableReferenceMap.get(table); + if (id >= 0) { + newTableIds[index] = id; + } else { + throw new IllegalArgumentException("No Table found for " + table); + } + } + + OnTableChangedObserver.OnTableChangedObserverWithIds wrapped = new OnTableChangedObserver.OnTableChangedObserverWithIds(observer, newTableIds); + synchronized (observerToObserverWithIdsMap) { + if (!observerToObserverWithIdsMap.containsKey(observer)) { + observerToObserverWithIdsMap.put(observer, wrapped); + + if (observingTableTracker.onAdded(newTableIds)) { + syncTriggers(); + } + } + } + } + + public void removeOnTableChangedObserver(OnTableChangedObserver observer) { + synchronized (observerToObserverWithIdsMap) { + OnTableChangedObserver.OnTableChangedObserverWithIds tableObserver = observerToObserverWithIdsMap.remove(observer); + if (observingTableTracker.onRemoved(tableObserver.tableIds)) { + syncTriggers(); + } + } + } + + /** + * Enqueues a table update check on the [DBFlowDatabase] Transaction queue. + */ + public void enqueueTableUpdateCheck() { + if (!pendingRefresh.compareAndSet(false, true)) { + Transaction.Builder builder = db.beginTransactionAsync((Function) databaseWrapper -> { + // TODO: ugly cast check here. + if (databaseWrapper == db) { + checkForTableUpdates((DBFlowDatabase)databaseWrapper); + } else { + throw new RuntimeException("Invalid DB object passed. Must be a "+DBFlowDatabase.class+""); + } + return null; + }); + builder.shouldRunInTransaction(false).execute(null,(transaction, throwable) -> { + FlowLog.log(FlowLog.Level.E, "Could not check for table updates", (Throwable) throwable); + return null; + },null,null); + } + } + + public void checkForTableUpdates() { + syncTriggers(); + checkForTableUpdates(db); + } + + public void construct(DatabaseWrapper db) { + synchronized (this) { + if (initialized) { + FlowLog.log(FlowLog.Level.W, "TableObserver already initialized"); + return; + } + + db.executeTransaction(unused -> { + db.execSQL("PRAGMA temp_store = MEMORY;"); + db.execSQL("PRAGMA recursive_triggers=ON;"); + db.execSQL("CREATE TEMP TABLE "+TABLE_OBSERVER_NAME+"("+TABLE_ID_COLUMN_NAME+" INTEGER PRIMARY KEY, "+INVALIDATED_COLUMN_NAME+" INTEGER NOT NULL DEFAULT 0);"); + return null; + }); + + syncTriggers(db); + initialized = true; + } + } + + public void syncTriggers() { + if (db.isOpened()) { + syncTriggers(db); + } + } + + public void syncTriggers(DatabaseWrapper db) { + if (db.isInTransaction()) { + // don't run in another transaction. + return; + } + + try { + while (true) { + Lock lock = this.db.getCloseLock(); + lock.lock(); + try { + ObservingTableTracker.Operation[] tablesToSync = observingTableTracker.tablesToSync(); + if (tablesToSync == null) { + return; + } + db.executeTransaction(unused -> { + for (int index = 0; index < tablesToSync.length; index++) { + ObservingTableTracker.Operation operation = tablesToSync[index]; + switch (operation) { + case Add: + observeTable(db, index); + break; + case Remove: + stopObservingTable(db, index); + break; + case None: + // don't do anything + break; + default: + break; + } + } + return null; + }); + observingTableTracker.syncCompleted(); + } finally { + lock.unlock(); + } + } + } catch (Exception e) { + if (e instanceof IllegalStateException || e instanceof SQLiteException) { + FlowLog.log(FlowLog.Level.E, "Cannot sync table TRIGGERs. Is the db closed?", e); + } else { + throw e; + } + } + } + + public void checkForTableUpdates(DBFlowDatabase db) { + Lock lock = db.getCloseLock(); + boolean hasUpdatedTable = false; + + try { + lock.lock(); + + if (!db.isOpened()) { + return; + } + + if (!initialized) { + db.openHelper().database(); + } + if (!initialized) { + FlowLog.log(FlowLog.Level.E, "Database is not initialized even though open. Is this an error?"); + return; + } + + if (!pendingRefresh.compareAndSet(true, false)) { + return; + } + + if (db.isInTransaction()) { + return; + } + + hasUpdatedTable = checkUpdatedTables(); + + } catch (Exception e) { + if (e instanceof IllegalStateException || e instanceof SQLiteException) { + FlowLog.log(FlowLog.Level.E, "Cannot check for table updates. is the db closed?", e); + } else { + throw e; + } + } finally { + lock.unlock(); + } + if (hasUpdatedTable) { + synchronized (observerToObserverWithIdsMap) { + Collection list = observerToObserverWithIdsMap.values(); + for (OnTableChangedObserver.OnTableChangedObserverWithIds observer : list) { + observer.notifyTables(tableStatus); + } + } + // reset + Arrays.fill(tableStatus, false); + } + } + + private boolean checkUpdatedTables() { + boolean hasUpdatedTable = false; + FlowCursor cursor = db.rawQuery(SELECT_UPDATED_TABLES_SQL, null); + while (cursor.goToNextRow()) { + int tableId = cursor.getInt(0); + tableStatus[tableId] = true; + hasUpdatedTable = true; + } + if (hasUpdatedTable) { + cleanupStatement().executeUpdateDelete(); + } + return hasUpdatedTable; + } + + private void observeTable(DatabaseWrapper db, int tableId) { + db.execSQL("INSERT OR IGNORE INTO "+TABLE_OBSERVER_NAME+" VALUES("+tableId+", 0)"); + Class tableName = tables.get(tableId); + + for (String method : TriggerMethod.METHODS){ + // utilize raw query, since we're using dynamic tables not supported by query language. + db.execSQL("CREATE TEMP TRIGGER IF NOT EXISTS " + getTriggerName(tableName, method) + + "AFTER " + method+ " ON " + StringUtils.quoteIfNeeded(FlowManager.getTableName(tableName)) + " BEGIN UPDATE " + TABLE_OBSERVER_NAME + " " + + "SET " + INVALIDATED_COLUMN_NAME + " = 1 " + + "WHERE " + TABLE_ID_COLUMN_NAME + " = " + tableId + " " + + "AND " + INVALIDATED_COLUMN_NAME + " = 0; END"); + } + + } + + private void stopObservingTable(DatabaseWrapper db, int tableId) { + Class tableName = tables.get(tableId); + + for (String method : TriggerMethod.METHODS){ + db.execSQL("DROP TRIGGER IF EXISTS " + getTriggerName(tableName, method)); + } + } + + private String getTriggerName(Class table, String method) { + String str = StringUtils.stripQuotes(FlowManager.getTableName(table)); + return "`" + TRIGGER_PREFIX + "_" + str + "_" + method + "`"; + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/Actionable.kt b/lib/src/main/java/com/dbflow5/query/Actionable.java similarity index 33% rename from lib/src/main/kotlin/com/dbflow5/query/Actionable.kt rename to lib/src/main/java/com/dbflow5/query/Actionable.java index e31c4afc0ff1323c24019bad9cc359b1b78b6124..43ddeaea94eec1d50fea4ab6a5b18c7ae5d991fe 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Actionable.kt +++ b/lib/src/main/java/com/dbflow5/query/Actionable.java @@ -1,11 +1,11 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.structure.ChangeAction +import com.dbflow5.structure.ChangeAction; /** * Description: Provides [Action] for SQL constructs. */ -interface Actionable { +public interface Actionable { - val primaryAction: ChangeAction + ChangeAction primaryAction(); } diff --git a/lib/src/main/java/com/dbflow5/query/BaseModelQueriable.java b/lib/src/main/java/com/dbflow5/query/BaseModelQueriable.java new file mode 100644 index 0000000000000000000000000000000000000000..74fbb3be8991444300eb72d2711b3ba21c5edfc5 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/BaseModelQueriable.java @@ -0,0 +1,125 @@ +package com.dbflow5.query; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.adapter.queriable.ListModelLoader; +import com.dbflow5.adapter.queriable.SingleModelLoader; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.list.FlowCursorList; +import com.dbflow5.query.list.FlowQueryList; +import com.dbflow5.sql.Query; +import ohos.eventhandler.EventHandler; + +import java.util.List; + +/** + * Description: Provides a base implementation of [ModelQueriable] to simplify a lot of code. It provides the + * default implementation for convenience. + */ +public abstract class BaseModelQueriable extends BaseQueriable implements ModelQueriable, Query { + + private RetrievalAdapter retrievalAdapter; + + private boolean cachingEnabled = true; + + private ListModelLoader _cacheListModelLoader = null; + + protected ListModelLoader listModelLoader() { + if (cachingEnabled) { + return retrievalAdapter.getListModelLoader(); + } else { + return retrievalAdapter.getNonCacheableListModelLoader(); + } + } + + protected SingleModelLoader singleModelLoader() { + if (cachingEnabled) { + return retrievalAdapter.getSingleModelLoader(); + } else { + return retrievalAdapter.getNonCacheableSingleModelLoader(); + } + } + + protected BaseModelQueriable(Class table){ + super(table); + retrievalAdapter = FlowManager.getRetrievalAdapter(table); + } + + @Override + public BaseModelQueriable disableCaching() { + cachingEnabled = false; + return this; + } + + @Override + public List queryList(DatabaseWrapper databaseWrapper) { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: " + query); + return listModelLoader().load(databaseWrapper, query); + } + + @Override + public TModel querySingle(DatabaseWrapper databaseWrapper) { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: "+query); + return singleModelLoader().load(databaseWrapper, query); + } + + @Override + public FlowCursorList cursorList(DatabaseWrapper databaseWrapper) { + return new FlowCursorList.Builder<>(this, databaseWrapper).build(); + } + + @Override + public FlowQueryList flowQueryList(DatabaseWrapper databaseWrapper) { + return new FlowQueryList.Builder<>(this, databaseWrapper, null).build(); + } + + @Override + public long executeUpdateDelete(DatabaseWrapper databaseWrapper) { + DatabaseStatement databaseStatement = compileStatement(databaseWrapper); + return databaseStatement.executeUpdateDelete(); + } + + @Override + public List queryCustomList(Class queryModelClass, DatabaseWrapper databaseWrapper) { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: "+query); + return getListQueryModelLoader(queryModelClass).load(databaseWrapper, query); + } + + @Override + public QueryClass queryCustomSingle(Class queryModelClass, DatabaseWrapper databaseWrapper) { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: "+query); + return getSingleQueryModelLoader(queryModelClass).load(databaseWrapper, query); + } + + + protected ListModelLoader getListQueryModelLoader(Class table) { + if (cachingEnabled) { + return FlowManager.retrievalAdapter(table).getListModelLoader(); + } else { + return FlowManager.retrievalAdapter(table).getNonCacheableListModelLoader(); + } + } + + protected SingleModelLoader getSingleQueryModelLoader(Class table) { + if (cachingEnabled) { + return FlowManager.retrievalAdapter(table).getSingleModelLoader(); + } else { + return FlowManager.retrievalAdapter(table).getNonCacheableSingleModelLoader(); + } + } + + /** + * Constructs a flowQueryList allowing a custom [Handler]. + */ + public static FlowQueryList flowQueryList( ModelQueriable modelQueriable, DatabaseWrapper databaseWrapper, EventHandler refreshHandler) { + return new FlowQueryList.Builder<>(modelQueriable, databaseWrapper, refreshHandler).build(); + } + +} + diff --git a/lib/src/main/java/com/dbflow5/query/BaseOperator.java b/lib/src/main/java/com/dbflow5/query/BaseOperator.java new file mode 100644 index 0000000000000000000000000000000000000000..2a839bc3255c30c3cf1cd63619499995ba662f2d --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/BaseOperator.java @@ -0,0 +1,256 @@ +package com.dbflow5.query; + +import com.dbflow5.SqlUtils; +import com.dbflow5.StringUtils; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.data.Blob; +import com.dbflow5.query.property.Property; +import com.dbflow5.sql.Query; + +/** + * Description: Base class for all kinds of [SQLOperator] + */ +public abstract class BaseOperator implements SQLOperator { + protected NameAlias nameAlias; + + /** + * The operation such as "=", "<", and more + */ + protected String operation = ""; + + /** + * The value of the column we care about + */ + protected Object value = null; + + /** + * A custom SQL statement after the value of the Operator + */ + protected String postArg = null; + + /** + * An optional separator to use when chaining these together + */ + protected String separator = null; + + /** + * If true, the value is set and we should append it. (to prevent false positive nulls) + */ + protected boolean isValueSet = false; + + public BaseOperator(NameAlias nameAlias){ + this.nameAlias = nameAlias; + } + + /** + * @return the value of the argument + */ + @Override + public Object value() { + return value; + } + + /** + * @return the column name + */ + @Override + public String columnName() { + if(nameAlias != null && nameAlias.getQuery() != null) { + return nameAlias.getQuery(); + } + return ""; + } + + @Override + public SQLOperator separator(String separator) { + this.separator = separator; + return this; + } + + @Override + public String separator() { + return separator; + } + + /** + * @return true if has a separator defined for this condition. + */ + @Override + public boolean hasSeparator() { + return StringUtils.isNotNullOrEmpty(separator); + } + + /** + * @return the operator such as "<", ">", or "=" + */ + @Override + public String operation() { + return operation; + } + + /** + * @return An optional post argument for this condition + */ + public String postArgument() { + return postArg; + } + + /** + * @return internal alias used for subclasses. + */ + public NameAlias columnAlias() { + return nameAlias; + } + + public String convertObjectToString(Object obj, boolean appendInnerParenthesis) { + return convertValueToString(obj, appendInnerParenthesis); + } + + public static String convertValueToString(Object value, boolean appendInnerQueryParenthesis) { + return convertValueToString(value, appendInnerQueryParenthesis, true); + } + + /** + * Converts a value input into a String representation of that. + * + * + * If it has a [TypeConverter], it first will convert it's value into its [TypeConverter.getDBValue]. + * + * + * If the value is a [Number], we return a string rep of that. + * + * + * If the value is a [BaseModelQueriable] and appendInnerQueryParenthesis is true, + * we return the query wrapped in "()" + * + * + * If the value is a [NameAlias], we return the [NameAlias.getQuery] + * + * + * If the value is a [SQLOperator], we [SQLOperator.appendConditionToQuery]. + * + * + * If the value is a [Query], we simply call [Query.getQuery]. + * + * + * If the value if a [Blob] or byte[] + * + * @param value The value of the column in Model format. + * @param appendInnerQueryParenthesis if its a [BaseModelQueriable] and an inner query value + * in a condition, we append parenthesis to the query. + * @return Returns the result as a string that's safe for SQLite. + */ + public static String convertValueToString(Object value, + boolean appendInnerQueryParenthesis, + boolean typeConvert) { + if (value == null) { + // Return to match NULL in SQLITE. + return "NULL"; + } else { + Object locVal = value; + if (typeConvert) { + TypeConverters.TypeConverter typeConverter = (TypeConverters.TypeConverter) FlowManager.getTypeConverterForClass(locVal.getClass()); + if (typeConverter != null) { + locVal = typeConverter.getDBValue(locVal); + } + } + if (appendInnerQueryParenthesis && locVal instanceof BaseModelQueriable) { + return ((BaseModelQueriable)locVal).enclosedQuery(); + } else if (locVal instanceof Property && locVal == Property.WILDCARD) { + return locVal.toString(); + } else { + if(locVal instanceof Number){ + return locVal.toString(); + }else if(locVal instanceof Enum){ + return SqlUtils.sqlEscapeString(((Enum) locVal).name()); + }else if(locVal instanceof NameAlias){ + return ((NameAlias) locVal).getQuery(); + }else if(locVal instanceof SQLOperator){ + StringBuilder queryBuilder = new StringBuilder(); + ((SQLOperator) locVal).appendConditionToQuery(queryBuilder); + + return queryBuilder.toString(); + }else if(locVal instanceof Query){ + return ((Query) locVal).getQuery(); + }else if(locVal instanceof Blob || locVal instanceof byte[]){ + byte[] bytes; + if(locVal instanceof Blob){ + bytes = ((Blob) locVal).getBlob(); + }else { + bytes = (byte[]) locVal; + } + return "X" + SqlUtils.sqlEscapeString(SqlUtils.byteArrayToHexString(bytes)); + }else { + return SqlUtils.sqlEscapeString(locVal.toString()); + } + } + } + } + + /** + * Returns a string containing the tokens joined by delimiters and converted into the property + * values for a query. + * + * @param delimiter The text to join the text with. + * @param tokens an [Iterable] of objects to be joined. Strings will be formed from + * the objects by calling [.convertValueToString]. + * @return A joined string + */ + public static String joinArguments(CharSequence delimiter, Iterable tokens, BaseOperator condition) { + StringBuilder builder = new StringBuilder(); + for(Object obj : tokens){ + String value = condition.convertObjectToString(obj, false); + builder.append(value); + builder.append(delimiter); + } + if(builder.length() > 0){ + builder.deleteCharAt(builder.length() - 1); + } + + return builder.toString(); + } + + /** + * Returns a string containing the tokens converted into DBValues joined by delimiters. + * + * @param delimiter The text to join the text with. + * @param tokens an array objects to be joined. Strings will be formed from + * the objects by calling object.toString(). + * @return A joined string + */ + public static String joinArguments(CharSequence delimiter, Object[] tokens) { + StringBuilder builder = new StringBuilder(); + for(Object obj : tokens){ + String value = convertValueToString(obj, false, true); + builder.append(value); + builder.append(delimiter); + } + if(builder.length() > 0){ + builder.deleteCharAt(builder.length() - 1); + } + + return builder.toString(); + } + + /** + * Returns a string containing the tokens converted into DBValues joined by delimiters. + * + * @param delimiter The text to join the text with. + * @param tokens an array objects to be joined. Strings will be formed from + * the objects by calling object.toString(). + * @return A joined string + */ + public static String joinArguments(CharSequence delimiter, Iterable tokens) { + StringBuilder builder = new StringBuilder(); + for(Object obj : tokens){ + String value = convertValueToString(obj, false, true); + builder.append(value); + builder.append(delimiter); + } + if(builder.length() > 0){ + builder.deleteCharAt(builder.length() - 1); + } + return builder.toString(); + } +} diff --git a/lib/src/main/java/com/dbflow5/query/BaseQueriable.java b/lib/src/main/java/com/dbflow5/query/BaseQueriable.java new file mode 100644 index 0000000000000000000000000000000000000000..5b9670af3aae6644004d5f53ddcc00787e795e48 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/BaseQueriable.java @@ -0,0 +1,99 @@ +package com.dbflow5.query; + +import com.dbflow5.SqlUtils; +import com.dbflow5.config.FlowLog; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseStatementWrapper; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.database.SQLiteException; +import com.dbflow5.runtime.NotifyDistributor; +import com.dbflow5.structure.ChangeAction; + +/** + * Description: Base implementation of something that can be queried from the database. + */ +public abstract class BaseQueriable implements Queriable, Actionable { + public Class table; + + protected BaseQueriable(Class table){ + this.table = table; + } + + @Override + public abstract ChangeAction primaryAction(); + + @Override + public long longValue(DatabaseWrapper databaseWrapper) { + try { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: "+query+""); + return SqlUtils.longForQuery(databaseWrapper, query); + } catch (SQLiteException sde) { + // catch exception here, log it but return 0; + FlowLog.logWarning(sde); + } + + return 0; + } + + @Override + public String stringValue(DatabaseWrapper databaseWrapper) { + try { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: "+query); + return SqlUtils.stringForQuery(databaseWrapper, query); + } catch (SQLiteException sde) { + // catch exception here, log it but return null; + FlowLog.logWarning(sde); + } + + return null; + } + + @Override + public boolean hasData(DatabaseWrapper databaseWrapper) { + return longValue(databaseWrapper) > 0; + } + + @Override + public FlowCursor cursor(DatabaseWrapper databaseWrapper) { + if (primaryAction() == ChangeAction.INSERT) { + // inserting, let's compile and insert + compileStatement(databaseWrapper).executeInsert(); + } else { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Executing query: " + query); + databaseWrapper.execSQL(query); + } + return null; + } + + @Override + public long executeInsert(DatabaseWrapper databaseWrapper) { + return compileStatement(databaseWrapper).executeInsert(); + } + + @Override + public void execute(DatabaseWrapper databaseWrapper) { + FlowCursor cursor = cursor(databaseWrapper); + if (cursor != null) { + cursor.close(); + } else { + // we dont query, we're executing something here. + NotifyDistributor.get().notifyTableChanged(table, primaryAction()); + } + } + + @Override + public DatabaseStatement compileStatement(DatabaseWrapper databaseWrapper) { + String query = getQuery(); + FlowLog.log(FlowLog.Level.V, "Compiling Query Into Statement: " + query); + return new DatabaseStatementWrapper<>(databaseWrapper.compileStatement(query), this); + } + + @Override + public String toString() { + return getQuery(); + } +} diff --git a/lib/src/main/java/com/dbflow5/query/BaseTransformable.java b/lib/src/main/java/com/dbflow5/query/BaseTransformable.java new file mode 100644 index 0000000000000000000000000000000000000000..21a39e0b8ba4637f340e5c30602860e76266adfe --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/BaseTransformable.java @@ -0,0 +1,108 @@ +package com.dbflow5.query; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.query.property.IProperty; + +import java.util.List; + +/** + * Description: Combines basic transformations and query ops into a base class. + */ +public abstract class BaseTransformable extends BaseModelQueriable implements Transformable, WhereBase { + private final Class tableClass; + + protected BaseTransformable(Class table){ + super(table); + this.tableClass = table; + } + + @Override + public Class table() { + return tableClass; + } + + public Where whereExists(Where where) { + return where().exists(where); + } + + public Where where(SQLOperator... conditions) { + return new Where<>(this, conditions); + } + + public Where where(SQLOperator condition) { + return new Where<>(this, condition); + } + + @Override + public FlowCursor cursor(DatabaseWrapper databaseWrapper) { + return where().cursor(databaseWrapper); + } + + @Override + public Where groupBy(NameAlias... nameAliases) { + return where().groupBy(nameAliases); + } + + @Override + public Where groupBy(IProperty... properties) { + return where().groupBy(properties); + } + + @Override + public Where orderBy(NameAlias nameAlias, boolean ascending) { + return where().orderBy(nameAlias, ascending); + } + + @Override + public Where orderBy(IProperty property, boolean ascending) { + return where().orderBy(property, ascending); + } + + @Override + public Where orderByAll(List orderByList) { + return where().orderByAll(orderByList); + } + + @Override + public Where orderBy(OrderBy orderBy) { + return where().orderBy(orderBy); + } + + @Override + public Where limit(long count) { + return where().limit(count); + } + + @Override + public Where offset(long offset) { + return where().offset(offset); + } + + @Override + public Where having(SQLOperator... conditions) { + return where().having(conditions); + } + + @Override + public abstract BaseTransformable cloneSelf(); + + @Override + public List queryList(DatabaseWrapper databaseWrapper) { + checkSelect("query"); + return super.queryList(databaseWrapper); + } + + @Override + public TModel querySingle(DatabaseWrapper databaseWrapper) { + checkSelect("query"); + limit(1); + return super.querySingle(databaseWrapper); + } + + private void checkSelect(String methodName) { + if (!(queryBuilderBase() instanceof Select)) { + throw new IllegalArgumentException("Please use "+methodName+"(). The beginning is not a Select"); + } + } +} diff --git a/lib/src/main/java/com/dbflow5/query/Case.java b/lib/src/main/java/com/dbflow5/query/Case.java new file mode 100644 index 0000000000000000000000000000000000000000..c6ac4d44a42763691009014184ab05104a7f817a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/Case.java @@ -0,0 +1,126 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.sql.Query; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: Represents a SQLITE CASE argument. + */ +public class Case implements Query { + private IProperty caseColumn = null; + private final List> caseConditions = new ArrayList<>(); + private String columnName = null; + private TReturn elseValue = null; + private boolean elseSpecified; + + // when true, only WHEN value is supported. Not WHEN condition + public boolean isEfficientCase; + + private boolean endSpecified; + + public Case(IProperty caseColumn) { + this.caseColumn = caseColumn; + if (this.caseColumn != null) { + isEfficientCase = true; + } + } + + @Override + public String getQuery() { + StringBuilder queryBuilder = new StringBuilder(" CASE"); + if (isEfficientCase) { + queryBuilder.append(" "); + queryBuilder.append(BaseOperator.convertValueToString(caseColumn, false)); + } + + StringBuilder builder = new StringBuilder(); + for(CaseCondition caseCondition : caseConditions) { + builder.append(caseCondition.toString()); + } + queryBuilder.append(builder.toString()); + + if (elseSpecified) { + queryBuilder.append(" ELSE ") + .append(BaseOperator.convertValueToString(elseValue, false)); + } + if (endSpecified) { + queryBuilder.append(" END "); + queryBuilder.append(columnName != null? columnName : ""); + } + return queryBuilder.toString(); + } + + public Case() { + this(null); + } + + //@JvmName("when") + public CaseCondition whenever(SQLOperator sqlOperator) { + if (isEfficientCase) { + throw new IllegalStateException("When using the efficient CASE method," + + "you must pass in value only, not condition."); + } + CaseCondition caseCondition = new CaseCondition<>(this, sqlOperator); + caseConditions.add(caseCondition); + return caseCondition; + } + + //@JvmName("when") + public CaseCondition whenever(TReturn whenValue) { + if (!isEfficientCase) { + throw new IllegalStateException("When not using the efficient CASE method, " + + "you must pass in the SQLOperator as a parameter"); + } + CaseCondition caseCondition = new CaseCondition<>(this, whenValue); + caseConditions.add(caseCondition); + return caseCondition; + } + + //@JvmName("when") + public CaseCondition whenever(IProperty property) { + if (!isEfficientCase) { + throw new IllegalStateException("When not using the efficient CASE method, " + + "you must pass in the SQLOperator as a parameter"); + } + CaseCondition caseCondition = new CaseCondition<>(this, property); + caseConditions.add(caseCondition); + return caseCondition; + } + + /** + * Default case here. If not specified, value will be NULL. + */ + //@JvmName("_else") + public Case _else(TReturn elseValue) { + this.elseValue = elseValue; + elseSpecified = true; // ensure its set especially if null specified. + return this; + } + + /** + * @param columnName The name of the case that we return in a column. + * @return The case completed as a property. + */ + public Property> end(String columnName) { + endSpecified = true; + if (columnName != null) { + this.columnName = StringUtils.quoteIfNeeded(columnName); + } + return new Property(null, NameAlias.rawBuilder(getQuery()).build()); + } + + /** + * @return The case complete as an operator. + */ + public Operator endAsOperator() { + return Operator.op(end(null).nameAlias()); + } + +} + + diff --git a/lib/src/main/java/com/dbflow5/query/CaseCondition.java b/lib/src/main/java/com/dbflow5/query/CaseCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..2920cc195c0c303b9b32b69fe3c9ec283a04b0c1 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/CaseCondition.java @@ -0,0 +1,77 @@ +package com.dbflow5.query; + +import com.dbflow5.query.property.IProperty; +import com.dbflow5.sql.Query; + +/** + * Description: Represents an individual condition inside a CASE. + */ +public class CaseCondition implements Query { + + private final Case caze; + private final TReturn whenValue; + private final SQLOperator sqlOperator; + private TReturn thenValue; + private final IProperty property; + private IProperty thenProperty = null; + private boolean isThenPropertySet; + + @Override + public String getQuery() { + StringBuilder builder = new StringBuilder(); + builder.append(" WHEN "); + if (caze.isEfficientCase) { + builder.append(BaseOperator.convertValueToString(property == null? whenValue : property, false)); + } else { + if(sqlOperator != null){ + sqlOperator.appendConditionToQuery(builder); + } + } + builder.append(" THEN "); + builder.append(BaseOperator.convertValueToString(isThenPropertySet? thenProperty : thenValue, false)); + + return builder.toString(); + } + + public CaseCondition(Case caze, SQLOperator sqlOperator) { + this.caze = caze; + this.sqlOperator = sqlOperator; + this.whenValue = null; + this.property = null; + } + + public CaseCondition(Case caze, TReturn whenValue) { + this.caze = caze; + this.whenValue = whenValue; + this.sqlOperator = null; + this.property = null; + } + + public CaseCondition(Case caze, IProperty property) { + this.caze = caze; + this.property = property; + this.whenValue = null; + this.sqlOperator = null; + } + + /** + * THEN part of this query, the value that gets set on column if condition is true. + */ + public Case then(TReturn value) { + thenValue = value; + return caze; + } + + public Case then(IProperty value) { + thenProperty = value; + + // in case values are null in some sense. + isThenPropertySet = true; + return caze; + } + + @Override + public String toString() { + return getQuery(); + } +} diff --git a/lib/src/main/java/com/dbflow5/query/CompletedTrigger.java b/lib/src/main/java/com/dbflow5/query/CompletedTrigger.java new file mode 100644 index 0000000000000000000000000000000000000000..2cfb57c08688c753fa76f71973067c6293162099 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/CompletedTrigger.java @@ -0,0 +1,62 @@ +package com.dbflow5.query; + +import com.dbflow5.SqlUtils; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.sql.Query; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: The last piece of a TRIGGER statement, this class contains the BEGIN...END and the logic in between. + */ +public class CompletedTrigger implements Query { + + private final TriggerMethod triggerMethod; + + /** + * The query to run between the BEGIN and END of this statement + */ + private final List triggerLogicQuery = new ArrayList<>(); + + public CompletedTrigger(TriggerMethod triggerMethod, Query triggerLogicQuery){ + this.triggerMethod = triggerMethod; + this.triggerLogicQuery.add(triggerLogicQuery); + } + + @Override + public String getQuery() { + StringBuilder builder = new StringBuilder(); + for (int index=0;index and(Query nextStatement) { + this.triggerLogicQuery.add(nextStatement); + return this; + } + + + /** + * Turns on this trigger + */ + public void enable(DatabaseWrapper databaseWrapper) { + databaseWrapper.execSQL(getQuery()); + } + + /** + * Disables this trigger + */ + public void disable(DatabaseWrapper databaseWrapper) { + SqlUtils.dropTrigger(databaseWrapper, triggerMethod.trigger.name); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/query/ContentValuesListener.kt b/lib/src/main/java/com/dbflow5/query/ContentValuesListener.java similarity index 74% rename from lib/src/main/kotlin/com/dbflow5/query/ContentValuesListener.kt rename to lib/src/main/java/com/dbflow5/query/ContentValuesListener.java index 5fa747da89b992c0c7f1ec4f9491951badd88b40..b5ba5bce2f10936380c068822a75b72b5c840d46 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/ContentValuesListener.kt +++ b/lib/src/main/java/com/dbflow5/query/ContentValuesListener.java @@ -1,9 +1,6 @@ -package com.dbflow5.query +package com.dbflow5.query; -import android.content.ContentValues -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.annotation.Table -import com.dbflow5.structure.Model +import ohos.data.rdb.ValuesBucket; /** * Description: Called after the declared [ContentValues] are binded. It enables @@ -15,10 +12,9 @@ import com.dbflow5.structure.Model * [ModelAdapter.bindToContentValues] * or [ModelAdapter.bindToInsertValues] with setting [Table.generateContentValues] to true. * - * @see SQLiteStatementListener */ -@Deprecated("") -interface ContentValuesListener { +@Deprecated +public interface ContentValuesListener { /** * Called during an [Model.update] and at the end of @@ -27,7 +23,7 @@ interface ContentValuesListener { * * @param contentValues The content values to bind to for an update. */ - fun onBindToContentValues(contentValues: ContentValues) + void onBindToContentValues(ValuesBucket contentValues); /** * Called during an [Model.update] and at the end of @@ -37,5 +33,5 @@ interface ContentValuesListener { * * @param contentValues The content values to insert into DB for a ContentProvider */ - fun onBindToInsertValues(contentValues: ContentValues) + void onBindToInsertValues(ValuesBucket contentValues); } diff --git a/lib/src/main/kotlin/com/dbflow5/query/Delete.kt b/lib/src/main/java/com/dbflow5/query/Delete.java similarity index 38% rename from lib/src/main/kotlin/com/dbflow5/query/Delete.kt rename to lib/src/main/java/com/dbflow5/query/Delete.java index 52e7f3fc4261f81ef39439687b77f237f4f111f0..6a1dfbee40c53fcd02d5764429e3139cf67aa20f 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Delete.kt +++ b/lib/src/main/java/com/dbflow5/query/Delete.java @@ -1,26 +1,29 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.sql.Query -import kotlin.reflect.KClass +import com.dbflow5.sql.Query; /** * Description: Constructs the beginning of a SQL DELETE query */ -class Delete internal constructor() : Query { +public class Delete implements Query { - override val query: String - get() = "DELETE " + public Delete() { + } + + @Override + public String getQuery() { + return "DELETE "; + } /** * Returns the new SQL FROM statement wrapper * * @param table The table we want to run this query from - * @param [T] The table class * @return [T] **/ - infix fun from(table: Class): From = From(this, table) - - infix fun from(table: KClass): From = from(table.java) + public From from(Class table) { + return new From<>(this, table, null); + } } diff --git a/lib/src/main/java/com/dbflow5/query/ExistenceOperator.java b/lib/src/main/java/com/dbflow5/query/ExistenceOperator.java new file mode 100644 index 0000000000000000000000000000000000000000..ffa83de920b626c0283295ae71d267cd94cd6e83 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/ExistenceOperator.java @@ -0,0 +1,55 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.sql.Query; + +/** + * Description: The condition that represents EXISTS in a SQL statement. + */ +public class ExistenceOperator implements SQLOperator, Query { + private final Where innerWhere; + + public ExistenceOperator(Where innerWhere){ + this.innerWhere = innerWhere; + } + + @Override + public void appendConditionToQuery(StringBuilder queryBuilder) { + StringUtils.appendQualifier(queryBuilder,"EXISTS", innerWhere.enclosedQuery()); + } + + @Override + public String columnName() { + throw new RuntimeException("Method not valid for ExistenceOperator"); + } + + @Override + public String separator() { + throw new RuntimeException("Method not valid for ExistenceOperator"); + } + + @Override + public SQLOperator separator(String separator) { + throw new RuntimeException("Method not valid for ExistenceOperator"); + } + + @Override + public boolean hasSeparator() { + return false; + } + + @Override + public String operation() { + return ""; + } + + @Override + public Object value() { + return innerWhere; + } + + @Override + public String getQuery() { + return appendToQuery(); + } +} diff --git a/lib/src/main/kotlin/com/dbflow5/query/From.kt b/lib/src/main/java/com/dbflow5/query/From.java similarity index 35% rename from lib/src/main/kotlin/com/dbflow5/query/From.kt rename to lib/src/main/java/com/dbflow5/query/From.java index 915268ec03f4a43498d874338afdc5f41470cefc..573c107470c4026a793800acfb2d66357fc3d6ae 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/From.kt +++ b/lib/src/main/java/com/dbflow5/query/From.java @@ -1,108 +1,118 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.config.FlowManager -import com.dbflow5.query.Join.JoinType -import com.dbflow5.query.property.IndexProperty -import com.dbflow5.sql.Query -import com.dbflow5.structure.ChangeAction -import kotlin.reflect.KClass -import kotlin.collections.Set as KSet +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.Join.JoinType; +import com.dbflow5.query.property.IndexProperty; +import com.dbflow5.sql.Query; +import com.dbflow5.structure.ChangeAction; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; /** * Description: The SQL FROM query wrapper that must have a [Query] base. */ -class From -/** - * The SQL from statement constructed. - * - * @param queryBuilderBase The base query we append this cursor to - * @param table The table this corresponds to - */ -internal constructor( +public class From extends BaseTransformable{ - /** - * @return The base query, usually a [Delete], [Select], or [Update] - */ - override val queryBuilderBase: Query, - table: Class, - - /** - * If specified, we use this as the subquery for the FROM statement. - */ - private val modelQueriable: ModelQueriable? = null) - : BaseTransformable(table) { + private final Query queryBuilderBase; + /** + * If specified, we use this as the subquery for the FROM statement. + */ + private final ModelQueriable modelQueriable; /** * An alias for the table */ - private var tableAlias: NameAlias? = null + private NameAlias tableAlias = null; /** * Enables the SQL JOIN statement */ - private val joins = arrayListOf>() + private final List> joins = new ArrayList<>(); - override val primaryAction: ChangeAction - get() = if (queryBuilderBase is Delete) ChangeAction.DELETE else ChangeAction.CHANGE + public From(Query queryBuilderBase, Class table, ModelQueriable modelQueriable){ + super(table); + this.queryBuilderBase = queryBuilderBase; + this.modelQueriable = modelQueriable; + } - override val query: String - get() { - val queryBuilder = StringBuilder() - .append(queryBuilderBase.query) - if (queryBuilderBase !is Update<*>) { - queryBuilder.append("FROM ") - } + @Override + public Query queryBuilderBase() { + return queryBuilderBase; + } + + @Override + public ChangeAction primaryAction() { + return queryBuilderBase instanceof Delete? ChangeAction.DELETE : ChangeAction.CHANGE; + } - modelQueriable?.let { queryBuilder.append(it.enclosedQuery) } - ?: queryBuilder.append(getTableAlias()) + @Override + public String getQuery() { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append(queryBuilderBase.getQuery()); + if (!(queryBuilderBase instanceof Update)) { + queryBuilder.append("FROM "); + } - if (queryBuilderBase is Select) { - if (!joins.isEmpty()) { - queryBuilder.append(" ") - } - joins.forEach { queryBuilder.append(it.query) } - } else { - queryBuilder.append(" ") - } + if(modelQueriable != null){ + queryBuilder.append(modelQueriable.enclosedQuery()); + }else { + queryBuilder.append(getTableAlias().toString()); + } - return queryBuilder.toString() + if (queryBuilderBase instanceof Select) { + if (!joins.isEmpty()) { + queryBuilder.append(" "); + } + joins.forEach(join -> queryBuilder.append(join.getQuery())); + } else { + queryBuilder.append(" "); } - override fun cloneSelf(): From { - val from = From( - when (queryBuilderBase) { - is Select -> queryBuilderBase.cloneSelf() - else -> queryBuilderBase - }, - table) - from.joins.addAll(joins) - from.tableAlias = tableAlias - return from + return queryBuilder.toString(); + } + + @Override + public From cloneSelf() { + From from = new From<>(queryBuilderBase instanceof Select? ((Select) queryBuilderBase).cloneSelf():queryBuilderBase, + table, null); + from.joins.addAll(joins); + from.tableAlias = tableAlias; + return from; } /** * @return A list of [Class] that represents tables represented in this query. For every * [Join] on another table, this adds another [Class]. */ - val associatedTables: KSet> - get() { - val tables = linkedSetOf>(table) - joins.mapTo(tables) { it.table } - return tables + public Set> associatedTables() { + LinkedHashSet> tables = new LinkedHashSet<>(Collections.singleton(table)); + for (Join join : joins) { + tables.add(join.table); } + return tables; + } - private fun getTableAlias(): NameAlias = tableAlias - ?: NameAlias.Builder(FlowManager.getTableName(table)).build().also { tableAlias = it } + private NameAlias getTableAlias() { + if(tableAlias == null){ + System.out.println("gaolll:"+FlowManager.getTableName(table)); + tableAlias = new NameAlias.Builder(FlowManager.getTableName(table)).build(); + } + return tableAlias; + } /** * Set an alias to the table name of this [From]. */ - infix fun `as`(alias: String): From { + public From as(String alias) { tableAlias = getTableAlias() .newBuilder() - .`as`(alias) - .build() - return this + .as(alias) + .build(); + return this; } /** @@ -111,10 +121,10 @@ internal constructor( * @param table The table this corresponds to * @param joinType The type of join to use */ - fun join(table: Class, joinType: JoinType): Join { - val join = Join(this, table, joinType) - joins.add(join) - return join + public Join join(Class table, JoinType joinType) { + Join join = new Join<>(this, table, joinType); + joins.add(join); + return join; } /** @@ -123,28 +133,21 @@ internal constructor( * @param modelQueriable A query we construct the [Join] from. * @param joinType The type of join to use. */ - fun join(modelQueriable: ModelQueriable, joinType: JoinType): Join { - val join = Join(this, joinType, modelQueriable) - joins.add(join) - return join + public Join join(ModelQueriable modelQueriable, JoinType joinType) { + Join join = new Join<>(this, joinType, modelQueriable); + joins.add(join); + return join; } - - infix fun innerJoin(joinTable: KClass): Join = join(joinTable.java, Join.JoinType.INNER) - - infix fun crossJoin(joinTable: KClass): Join = join(joinTable.java, Join.JoinType.CROSS) - - infix fun leftOuterJoin(joinTable: KClass): Join = join(joinTable.java, Join.JoinType.LEFT_OUTER) - - infix fun naturalJoin(joinTable: KClass): Join = join(joinTable.java, Join.JoinType.NATURAL) - /** * Adds a [JoinType.CROSS] join on a specific table for this query. * * @param table The table to join on. * @param The class of the join table. */ - fun crossJoin(table: Class): Join = join(table, JoinType.CROSS) + public Join crossJoin(Class table) { + return join(table, JoinType.CROSS); + } /** * Adds a [JoinType.CROSS] join on a specific table for this query. @@ -152,8 +155,9 @@ internal constructor( * @param modelQueriable The query to join on. * @param The class of the join table. */ - fun crossJoin(modelQueriable: ModelQueriable): Join = - join(modelQueriable, JoinType.CROSS) + public Join crossJoin(ModelQueriable modelQueriable) { + return join(modelQueriable, JoinType.CROSS); + } /** * Adds a [JoinType.INNER] join on a specific table for this query. @@ -161,7 +165,9 @@ internal constructor( * @param table The table to join on. * @param The class of the join table. */ - fun innerJoin(table: Class): Join = join(table, JoinType.INNER) + public Join innerJoin(Class table) { + return join(table, JoinType.INNER); + } /** * Adds a [JoinType.INNER] join on a specific table for this query. @@ -169,8 +175,9 @@ internal constructor( * @param modelQueriable The query to join on. * @param The class of the join table. */ - fun innerJoin(modelQueriable: ModelQueriable): Join = - join(modelQueriable, JoinType.INNER) + public Join innerJoin(ModelQueriable modelQueriable) { + return join(modelQueriable, JoinType.INNER); + } /** * Adds a [JoinType.LEFT_OUTER] join on a specific table for this query. @@ -178,8 +185,9 @@ internal constructor( * @param table The table to join on. * @param The class of the join table. */ - fun leftOuterJoin(table: Class): Join = - join(table, JoinType.LEFT_OUTER) + public Join leftOuterJoin(Class table) { + return join(table, JoinType.LEFT_OUTER); + } /** * Adds a [JoinType.LEFT_OUTER] join on a specific table for this query. @@ -187,9 +195,9 @@ internal constructor( * @param modelQueriable The query to join on. * @param The class of the join table. */ - fun leftOuterJoin(modelQueriable: ModelQueriable): Join = - join(modelQueriable, JoinType.LEFT_OUTER) - + public Join leftOuterJoin(ModelQueriable modelQueriable) { + return join(modelQueriable, JoinType.LEFT_OUTER); + } /** * Adds a [JoinType.NATURAL] join on a specific table for this query. @@ -197,8 +205,9 @@ internal constructor( * @param table The table to join on. * @param The class of the join table. */ - fun naturalJoin(table: Class): Join = - join(table, JoinType.NATURAL) + public Join naturalJoin(Class table) { + return join(table, JoinType.NATURAL); + } /** * Adds a [JoinType.NATURAL] join on a specific table for this query. @@ -206,29 +215,22 @@ internal constructor( * @param modelQueriable The query to join on. * @param The class of the join table. */ - fun naturalJoin(modelQueriable: ModelQueriable): Join = - join(modelQueriable, JoinType.NATURAL) + public Join naturalJoin(ModelQueriable modelQueriable) { + return join(modelQueriable, JoinType.NATURAL); + } /** * Begins an INDEXED BY piece of this query with the specified name. * * @param indexProperty The index property generated. */ - fun indexedBy(indexProperty: IndexProperty): IndexedBy = - IndexedBy(indexProperty, this) - -} + public IndexedBy indexedBy(IndexProperty indexProperty) { + return new IndexedBy<>(indexProperty, this); + } -/** - * Extracts the [From] from a [ModelQueriable] if possible to get [From.associatedTables] - */ -@Suppress("UNCHECKED_CAST") -fun ModelQueriable.extractFrom(): From? { - return if (this is From<*>) { - this as From - } else if (this is Where<*> && whereBase is From<*>) { - whereBase as From - } else { - null + @Override + public Class table() { + return table; } } + diff --git a/lib/src/main/java/com/dbflow5/query/IConditional.java b/lib/src/main/java/com/dbflow5/query/IConditional.java new file mode 100644 index 0000000000000000000000000000000000000000..93f68ba492a0a44192e3c24086a76e15611bdbaa --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/IConditional.java @@ -0,0 +1,127 @@ +package com.dbflow5.query; + +import com.dbflow5.sql.Query; + +import java.util.Arrays; + +/** + * Description: Simple interface for objects that can be used as [Operator]. This class + * takes no type parameters for primitive objects. + */ +public interface IConditional extends Query { + + Operator isNull(); + + Operator isNotNull(); + + Operator is(IConditional conditional); + + Operator is(BaseModelQueriable baseModelQueriable); + + Operator eq(IConditional conditional); + + Operator eq(BaseModelQueriable baseModelQueriable); + + Operator concatenate(IConditional conditional); + + Operator isNot(IConditional conditional); + + Operator isNot(BaseModelQueriable baseModelQueriable); + + Operator notEq(IConditional conditional); + + Operator notEq(BaseModelQueriable baseModelQueriable); + + Operator like(IConditional conditional); + + Operator like(BaseModelQueriable baseModelQueriable); + + Operator notLike(IConditional conditional); + + Operator notLike(BaseModelQueriable baseModelQueriable); + + Operator glob(IConditional conditional); + + Operator glob(BaseModelQueriable baseModelQueriable); + + Operator like(String value); + + Operator match(String value); + + Operator notLike(String value); + + Operator glob(String value); + + Operator greaterThan(IConditional conditional); + + Operator greaterThan(BaseModelQueriable baseModelQueriable); + + Operator greaterThanOrEq(IConditional conditional); + + Operator greaterThanOrEq(BaseModelQueriable baseModelQueriable); + + Operator lessThan(IConditional conditional); + + Operator lessThan(BaseModelQueriable baseModelQueriable); + + Operator lessThanOrEq(IConditional conditional); + + Operator lessThanOrEq(BaseModelQueriable baseModelQueriable); + + Operator.Between between(IConditional conditional); + + Operator.Between between(BaseModelQueriable baseModelQueriable); + + Operator.In in(IConditional firstConditional, IConditional... conditionals); + + Operator.In in(BaseModelQueriable firstBaseModelQueriable, + BaseModelQueriable... baseModelQueriables); + + Operator.In notIn(IConditional firstConditional, IConditional... conditionals); + + Operator.In notIn(BaseModelQueriable firstBaseModelQueriable, + BaseModelQueriable... baseModelQueriables); + + Operator plus(BaseModelQueriable value); + + Operator minus(BaseModelQueriable value); + + Operator div(BaseModelQueriable value); + + Operator times(BaseModelQueriable value); + + Operator rem(BaseModelQueriable value); + + default Operator.In in(IConditional[] values) { + if(values.length == 1){ + return in(values[0]); + }else { + return this.in(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } + + default Operator.In notIn(IConditional[] values) { + if(values.length == 1){ + return notIn(values[0]); + }else { + return this.notIn(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } + + default Operator.In in(BaseModelQueriable[] values) { + if(values.length == 1){ + return in(values[0]); + }else { + return this.in(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } + + default Operator.In notIn(BaseModelQueriable[] values) { + if(values.length == 1){ + return notIn(values[0]); + }else { + return this.notIn(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } +} + diff --git a/lib/src/main/kotlin/com/dbflow5/query/IOperator.kt b/lib/src/main/java/com/dbflow5/query/IOperator.java similarity index 79% rename from lib/src/main/kotlin/com/dbflow5/query/IOperator.kt rename to lib/src/main/java/com/dbflow5/query/IOperator.java index 37622a1b95f0cfbd6fc75d360c34a288490ff38b..316447521c820d7888d4d7c084616f79a91a8930 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/IOperator.kt +++ b/lib/src/main/java/com/dbflow5/query/IOperator.java @@ -1,11 +1,13 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.sql.Query +import com.dbflow5.sql.Query; + +import java.util.Collection; /** * Description: Interface for objects that can be used as [Operator] that have a type parameter. */ -interface IOperator : Query, IConditional { +public interface IOperator extends Query, IConditional { /** * Assigns the operation to "=" @@ -13,7 +15,7 @@ interface IOperator : Query, IConditional { * @param value The [T] that we express equality to. * @return A [Operator] that represents equality between this and the parameter. */ - infix fun `is`(value: T?): Operator + Operator is(T value); /** * Assigns the operation to "=". Identical to [.is] @@ -22,7 +24,7 @@ interface IOperator : Query, IConditional { * @return A [Operator] that represents equality between this and the parameter. * @see .is */ - infix fun eq(value: T?): Operator + Operator eq(T value); /** * Generates a [Operator] that concatenates this [IOperator] with the [T] via "||" @@ -31,7 +33,7 @@ interface IOperator : Query, IConditional { * @param value The value to concatenate. * @return A [<] that represents concatenation. */ - infix fun concatenate(value: Any?): Operator + Operator concatenate(Object value); /** * Assigns the operation to "!=" @@ -39,7 +41,7 @@ interface IOperator : Query, IConditional { * @param value The [T] that we express inequality to. * @return A [<] that represents inequality between this and the parameter. */ - infix fun isNot(value: T?): Operator + Operator isNot(T value); /** * Assigns the operation to "!=" @@ -48,7 +50,7 @@ interface IOperator : Query, IConditional { * @return A [<] that represents inequality between this and the parameter. * @see .notEq */ - infix fun notEq(value: T?): Operator + Operator notEq(T value); /** * Assigns operation to ">" @@ -56,7 +58,7 @@ interface IOperator : Query, IConditional { * @param value The [T] that this [IOperator] is greater than. * @return A [<] that represents greater than between this and the parameter. */ - infix fun greaterThan(value: T): Operator + Operator greaterThan(T value); /** * Assigns operation to ">=" @@ -64,7 +66,7 @@ interface IOperator : Query, IConditional { * @param value The [T] that this [IOperator] is greater than or equal to. * @return A [<] that represents greater than or equal between this and the parameter. */ - infix fun greaterThanOrEq(value: T): Operator + Operator greaterThanOrEq(T value); /** @@ -73,7 +75,7 @@ interface IOperator : Query, IConditional { * @param value The [T] that this [IOperator] is less than. * @return A [<] that represents less than between this and the parameter. */ - infix fun lessThan(value: T): Operator + Operator lessThan(T value); /** @@ -82,9 +84,9 @@ interface IOperator : Query, IConditional { * @param value The [T] that this [IOperator] is less than or equal to. * @return A [<] that represents less than or equal to between this and the parameter. */ - infix fun lessThanOrEq(value: T): Operator + Operator lessThanOrEq(T value); - infix fun between(value: T): Operator.Between + Operator.Between between(T value); /** * Turns this [IOperator] into an [.In][<]. It means that this object should @@ -94,7 +96,7 @@ interface IOperator : Query, IConditional { * @param values The rest of the values to pass optionally. * @return A new [.In][<] built from this [IOperator]. */ - fun `in`(firstValue: T, vararg values: T): Operator.In + Operator.In in(T firstValue, T... values); /** * Turns this [IOperator] into an [.In][<] (not). It means that this object should NOT @@ -104,7 +106,7 @@ interface IOperator : Query, IConditional { * @param values The rest of the values to pass optionally. * @return A new [.In][<] (not) built from this [IOperator]. */ - fun notIn(firstValue: T, vararg values: T): Operator.In + Operator.In notIn(T firstValue, T... values); /** * Turns this [IOperator] into an [.In][<]. It means that this object should @@ -113,7 +115,7 @@ interface IOperator : Query, IConditional { * @param values The rest of the values to pass optionally. * @return A new [.In][<] built from this [IOperator]. */ - infix fun `in`(values: Collection): Operator.In + Operator.In in(Collection values); /** * Turns this [IOperator] into an [.In][<] (not). It means that this object should NOT @@ -122,21 +124,21 @@ interface IOperator : Query, IConditional { * @param values The rest of the values to pass optionally. * @return A new [.In][<] (not) built from this [IOperator]. */ - infix fun notIn(values: Collection): Operator.In + Operator.In notIn(Collection values); /** * Adds another value and returns the operator. i.e p1 + p2 * * @param value the value to add. */ - infix operator fun plus(value: T): Operator + Operator plus(T value); /** * Subtracts another value and returns the operator. i.e p1 - p2 * * @param value the value to subtract. */ - infix operator fun minus(value: T): Operator + Operator minus(T value); /** * Divides another value and returns as the operator. i.e p1 / p2 @@ -144,19 +146,19 @@ interface IOperator : Query, IConditional { * @param value the value to divide. * @return A new instance. */ - infix operator fun div(value: T): Operator + Operator div(T value); /** * Multiplies another value and returns as the operator. i.e p1 * p2 * * @param value the value to multiply. */ - infix operator fun times(value: T): Operator + Operator times(T value); /** * Modulous another value and returns as the operator. i.e p1 % p2 * * @param value the value to calculate remainder of. */ - infix operator fun rem(value: T): Operator + Operator rem(T value); } diff --git a/lib/src/main/kotlin/com/dbflow5/query/Index.kt b/lib/src/main/java/com/dbflow5/query/Index.java similarity index 35% rename from lib/src/main/kotlin/com/dbflow5/query/Index.kt rename to lib/src/main/java/com/dbflow5/query/Index.java index a8da47a381b461d33f61fc43875d174f97d7ece5..b38f702222fe60e372f97466f0b5d22817f5c89e 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Index.kt +++ b/lib/src/main/java/com/dbflow5/query/Index.java @@ -1,49 +1,54 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.appendList -import com.dbflow5.appendQuotedIfNeeded -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.SQLiteException -import com.dbflow5.dropIndex -import com.dbflow5.query.property.IProperty -import com.dbflow5.sql.Query +import com.dbflow5.SqlUtils; +import com.dbflow5.StringUtils; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.sql.Query; + +import java.util.ArrayList; +import java.util.List; /** * Description: an INDEX class that enables you to index a specific column from a table. This enables * faster retrieval on tables, while increasing the database file size. So enable/disable these as necessary. */ -class Index -/** - * Creates a new index with the specified name - * - * @param indexName The name of this index. - */ -( +public class Index implements Query { /** * @return The name of this index. */ - val indexName: String, + String indexName; /** * @return The table this INDEX belongs to. */ - val table: Class) : Query { - private val columns: MutableList = arrayListOf() + Class table; + + private final List columns = new ArrayList<>(); /** * @return true if the index is unique */ - var isUnique = false - private set - - override val query: String - get() = buildString { - append("CREATE ") - append(if (isUnique) "UNIQUE " else "") - append("INDEX IF NOT EXISTS ") - appendQuotedIfNeeded(indexName) - append(" ON ").append(FlowManager.getTableName(table)) - append("(").appendList(columns).append(")") - } + boolean isUnique = false; + + public Index(String indexName, Class table){ + this.indexName = indexName; + this.table = table; + } + + @Override + public String getQuery() { + StringBuilder builder = new StringBuilder(); + builder.append("CREATE "); + builder.append(isUnique? "UNIQUE " : ""); + builder.append("INDEX IF NOT EXISTS "); + System.out.println("indexName:"+indexName); + StringUtils.appendQuotedIfNeeded(builder, indexName); + builder.append(" ON ").append(FlowManager.getTableName(table)); + builder.append("("); + StringUtils.appendList(builder, columns); + builder.append(")"); + return builder.toString(); + } /** * If true, will append the UNIQUE statement to this trigger. @@ -51,31 +56,37 @@ class Index * @param unique true if unique. If created again, a [SQLiteException] is thrown. * @return This instance. */ - fun unique(unique: Boolean) = apply { - isUnique = unique + public Index unique(boolean unique) { + isUnique = unique; + return this; } /** * The table to execute this Index on. * - * @param table The table to execute index on. * @param properties The properties to create an index for. * @return This instance. */ - fun on(vararg properties: IProperty<*>) = apply { - properties.forEach { and(it) } + public Index on(IProperty... properties) { + for(IProperty property : properties){ + and(property); + } + return this; } /** * The table to execute this Index on. * - * @param table The table to execute index on. + * @param firstAlias The table to execute index on. * @param columns The columns to create an index for. * @return This instance. */ - fun on(firstAlias: NameAlias, vararg columns: NameAlias) = apply { - and(firstAlias) - columns.forEach { and(it) } + public Index on(NameAlias firstAlias, NameAlias... columns) { + and(firstAlias); + for(NameAlias nameAlias : columns){ + and(nameAlias); + } + return this; } /** @@ -84,10 +95,11 @@ class Index * @param property The name of the column. If already exists, this op will not be added * @return This instance. */ - fun and(property: IProperty<*>) = apply { - if (!columns.contains(property.nameAlias)) { - columns.add(property.nameAlias) + public Index and(IProperty property) { + if (!columns.contains(property.nameAlias())) { + columns.add(property.nameAlias()); } + return this; } /** @@ -96,30 +108,32 @@ class Index * @param columnName The name of the column. If already exists, this op will not be added * @return This instance. */ - fun and(columnName: NameAlias) = apply { + public Index and(NameAlias columnName) { if (!columns.contains(columnName)) { - columns.add(columnName) + columns.add(columnName); } + return this; } - fun createIfNotExists(databaseWrapper: DatabaseWrapper) { + public void createIfNotExists(DatabaseWrapper databaseWrapper) { if (columns.isEmpty()) { - throw IllegalStateException("There should be at least one column in this index") + throw new IllegalStateException("There should be at least one column in this index"); } - databaseWrapper.execSQL(query) + databaseWrapper.execSQL(getQuery()); } - fun drop(databaseWrapper: DatabaseWrapper) { - dropIndex(databaseWrapper, indexName) + public void drop(DatabaseWrapper databaseWrapper) { + SqlUtils.dropIndex(databaseWrapper, indexName); } -} + public static Index indexOn(Class clazz, String indexName, IProperty... property) { + return SQLite.index(indexName, clazz).on(property); + } + + public static Index indexOn(Class clazz, String indexName, NameAlias firstNameAlias, NameAlias... arrayOfNameAlias) { + return SQLite.index(indexName, clazz).on(firstNameAlias, arrayOfNameAlias); + } +} -inline fun indexOn(indexName: String, - vararg property: IProperty<*>) - = index(indexName, T::class).on(*property) -inline fun indexOn(indexName: String, firstNameAlias: NameAlias, - vararg arrayOfNameAlias: NameAlias) - = index(indexName, T::class).on(firstNameAlias, *arrayOfNameAlias) diff --git a/lib/src/main/java/com/dbflow5/query/IndexedBy.java b/lib/src/main/java/com/dbflow5/query/IndexedBy.java new file mode 100644 index 0000000000000000000000000000000000000000..6f7d5927efb70a1467e6a74a7d34b7615c89c8f8 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/IndexedBy.java @@ -0,0 +1,50 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.query.property.IndexProperty; +import com.dbflow5.sql.Query; +import com.dbflow5.structure.ChangeAction; + +/** + * Description: The INDEXED BY part of a SELECT/UPDATE/DELETE + */ +public class IndexedBy extends BaseTransformable { + private final IndexProperty indexProperty; + private final WhereBase whereBase; + + public IndexedBy(IndexProperty indexProperty, WhereBase whereBase) { + super(whereBase.table()); + this.indexProperty = indexProperty; + this.whereBase = whereBase; + } + + @Override + public Query queryBuilderBase() { + return whereBase.queryBuilderBase(); + } + + @Override + public String getQuery() { + StringBuilder builder = new StringBuilder(); + builder.append(whereBase.getQuery()); + builder.append(" INDEXED BY "); + builder.append(StringUtils.quoteIfNeeded(indexProperty.indexName)); + builder.append(" "); + return builder.toString(); + } + + @Override + public ChangeAction primaryAction() { + return whereBase.primaryAction(); + } + + @Override + public IndexedBy cloneSelf() { + return new IndexedBy<>(indexProperty, whereBase.cloneSelf()); + } + + @Override + public Class table() { + return table; + } +} diff --git a/lib/src/main/java/com/dbflow5/query/Insert.java b/lib/src/main/java/com/dbflow5/query/Insert.java new file mode 100644 index 0000000000000000000000000000000000000000..e510ecaf25e7ef7930030e841797e1ea5e60b7d3 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/Insert.java @@ -0,0 +1,331 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.sql.Query; +import com.dbflow5.structure.ChangeAction; +import ohos.data.rdb.ValuesBucket; +import ohos.utils.Pair; + +import java.util.*; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Description: The SQLite INSERT command + */ +public class Insert extends BaseQueriable implements Query { + + /** + * The columns to specify in this query (optional) + */ + private List> columns = null; + + /** + * The values to specify in this query + */ + private List> valuesList = new ArrayList<>(); + + /** + * The conflict algorithm to use to resolve inserts. + */ + private ConflictAction conflictAction = ConflictAction.NONE; + + private From selectFrom = null; + + public Insert(Class table, Property... columns) { + super(table); + columns(columns); + } + + @Override// append FROM, which overrides values + public String getQuery() { + StringBuilder q = new StringBuilder(); + q.append("INSERT "); + if (conflictAction != null && conflictAction != ConflictAction.NONE) { + q.append("OR "+conflictAction+" "); + } + q.append("INTO "); + q.append(FlowManager.getTableName(table)); + if(columns != null){ + if (!columns.isEmpty()) { + q.append("("); + StringUtils.appendArray(q, columns.toArray()); + q.append(")"); + } + } + From selectFrom = this.selectFrom; + if (selectFrom != null) { + q.append(" "); + q.append(selectFrom.getQuery()); + } else { + if (valuesList.size() < 1) { + throw new IllegalStateException("The insert of "+FlowManager.getTableName(table)+" " + + "should have at least one value specified for the insert"); + } else { + if(columns != null && !columns.isEmpty()){ + valuesList.stream().filter(objects -> objects.size() != columns.size()).forEach(new Consumer>() { + @Override + public void accept(List objects) { + throw new IllegalStateException( + "The Insert of "+ FlowManager.getTableName(table) + + "|when specifying columns needs to have the same amount"+ + "|of values and columns. found ${it.size} != ${columns.size}"); + } + }); + + } + } + + q.append(" VALUES("); + for(int i=0;i 0) { + q.append(",("); + } + q.append(BaseOperator.joinArguments(", ", Collections.singleton(valuesList))); + q.append(")"); + } + } + return q.toString(); + } + + @Override + public ChangeAction primaryAction() { + return ChangeAction.INSERT; + } + + /** + * The optional columns to specify. If specified, the values length must correspond to these columns, and + * each column has a 1-1 relationship to the values. + * + * @param columns The columns to use + */ + public Insert columns(String... columns) { + ModelAdapter modelClassModelAdapter = FlowManager.modelAdapter(table); + for(String column : columns) { + Property property = modelClassModelAdapter.getProperty(column); + this.columns.add(property); + } + return this; + } + + public Insert columns(IProperty... properties) { + this.columns = Arrays.asList(properties); + return this; + } + + public Insert columns(List> properties) { + this.columns = properties; + return this; + } + + /** + * @return Appends a list of columns to this INSERT statement from the associated [TModel]. + */ + public Insert asColumns() { + columns(FlowManager.modelAdapter(table).getAllColumnProperties()); + return this; + } + + /** + * @return Appends a list of columns to this INSERT and ? as the values. + */ + public Insert asColumnValues() { + asColumns(); + if(columns != null){ + List list = new ArrayList<>(); + for(IProperty property : columns){ + list.add("?"); + } + valuesList.add(Collections.singletonList(list)); + } + return this; + } + + /** + * The required values to specify. It must be non-empty and match the length of the columns when + * a set of columns are specified. + * + * @param values The non type-converted values + */ + public Insert values(Object... values) { + valuesList.add(Arrays.asList(values)); + return this; + } + + /** + * The required values to specify. It must be non-empty and match the length of the columns when + * a set of columns are specified. + * + * @param values The non type-converted values + */ + public Insert values(List values) { + valuesList.add(Collections.singletonList(values)); + return this; + } + + /** + * Uses the [Operator] pairs to fill this insert query. + * + * @param conditions The conditions that we use to fill the columns and values of this INSERT + */ + public Insert columnValues(SQLOperator... conditions) { + String[] columnNames = new String[conditions.length]; + Object[] values = new Object[conditions.length]; + int index = 0; + for(SQLOperator sqlOperator : conditions){ + columnNames[index] = sqlOperator.columnName(); + values[index] = sqlOperator.value(); + index++; + } + return columns(columnNames).values(values); + } + + /** + * Uses the [Operator] pairs to fill this insert query. + * + * @param conditions The conditions that we use to fill the columns and values of this INSERT + */ + public Insert columnValues(Pair, ?>... conditions) { + IProperty[] columnNames = new IProperty[conditions.length]; + Object[] values = new Object[conditions.length]; + int index = 0; + for(Pair, ?> pair : conditions){ + columnNames[index] = pair.f; + values[index] = pair.s; + index++; + } + return columns(columnNames).values(values); + } + + /** + * Uses the [Operator] pairs to fill this insert query. + * + * @param group The [Iterable] of [SQLOperator] + */ + public Insert columnValues(Iterable group) { + List columnNames = new ArrayList<>(); + List values = new ArrayList<>(); + for(SQLOperator sqlOperator : group){ + columnNames.add(sqlOperator.columnName()); + values.add(sqlOperator.value()); + } + return columns(columnNames.toArray(new String[0])).values(values); + } + + public Insert columnValues(ValuesBucket contentValues) { + Set> entries = contentValues.getAll(); + int size = contentValues.getColumnSet().size(); + String[] columns = new String[size]; + Object[] values = new Object[size]; + int index = 0; + for (Map.Entry entry : entries) { + columns[index] = entry.getKey(); + values[index] = contentValues.getObject(entry.getKey()); + } + + return columns(columns).values(values); + } + + /** + * Appends the specified [From], which comes from a [Select] statement. + * + * @param selectFrom The from that is continuation of [Select]. + * @return this + */ + public Insert select(From selectFrom) { + this.selectFrom = selectFrom; + return this; + } + + + /** + * Specifies the optional OR method to use for this insert query + * + * @param action The conflict action to use + * @return this + */ + public Insert or(ConflictAction action) { + conflictAction = action; + return this; + } + + /** + * Specifies OR REPLACE, which will either insert if row does not exist, or replace the value if it does. + * + * @return this + */ + public Insert orReplace() { + return or(ConflictAction.REPLACE); + } + + /** + * Specifies OR ROLLBACK, which will cancel the current transaction or ABORT the current statement. + * + * @return this + */ + public Insert orRollback() { + return or(ConflictAction.ROLLBACK); + } + + /** + * Specifies OR ABORT, which will cancel the current INSERT, but all other operations will be preserved in + * the current transaction. + * + * @return this + */ + public Insert orAbort() { + return or(ConflictAction.ABORT); + } + + /** + * Specifies OR FAIL, which does not back out of the previous statements. Anything else in the current + * transaction will fail. + * + * @return this + */ + public Insert orFail() { + return or(ConflictAction.FAIL); + } + + /** + * Specifies OR IGNORE, which ignores any kind of error and proceeds as normal. + * + * @return this + */ + public Insert orIgnore() { + return or(ConflictAction.IGNORE); + } + + @Override + public long executeUpdateDelete(DatabaseWrapper databaseWrapper) { + throw new IllegalStateException("Cannot call executeUpdateDelete() from an Insert"); + } + + + public Insert orReplace(Pair, ?>[] into) { + return orReplace().columnValues(into); + } + + public Insert orRollback(Pair, ?>[] into) { + return orRollback().columnValues(into); + } + + public Insert orAbort(Pair, ?>[] into) { + return orAbort().columnValues(into); + } + + public Insert orFail(Pair, ?>[] into) { + return orFail().columnValues(into); + } + + public Insert orIgnore(Pair, ?>[] into) { + return orIgnore().columnValues(into); + } + +} diff --git a/lib/src/main/kotlin/com/dbflow5/query/Join.kt b/lib/src/main/java/com/dbflow5/query/Join.java similarity index 53% rename from lib/src/main/kotlin/com/dbflow5/query/Join.kt rename to lib/src/main/java/com/dbflow5/query/Join.java index c5c9bee47aca3437420428fde884f96483ccc39a..f3233feff04d75460d1e7484cf94b9dff83a27bd 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Join.kt +++ b/lib/src/main/java/com/dbflow5/query/Join.java @@ -1,73 +1,78 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.appendList -import com.dbflow5.config.FlowManager -import com.dbflow5.query.property.IProperty -import com.dbflow5.query.property.PropertyFactory -import com.dbflow5.sql.Query +import com.dbflow5.StringUtils; +import com.dbflow5.config.FlowManager; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.PropertyFactory; +import com.dbflow5.sql.Query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * Description: Specifies a SQLite JOIN statement */ -class Join : Query { +public class Join implements Query { - val table: Class + Class table; /** * The type of JOIN to use */ - private var type: JoinType + private final JoinType type; /** * The FROM statement that prefixes this statement. */ - private var from: From + private final From from; /** * The alias to name the JOIN */ - private var alias: NameAlias + private NameAlias alias; /** * The ON conditions */ - private var onGroup: OperatorGroup? = null + private OperatorGroup onGroup = null; /** * What columns to use. */ - private val using = arrayListOf>() - - override// natural joins do no have on or using clauses. - val query: String - get() { - val queryBuilder = StringBuilder() - - queryBuilder.append(type.name.replace("_", " ")).append(" ") - - queryBuilder.append("JOIN") - .append(" ") - .append(alias.fullQuery) - .append(" ") - if (JoinType.NATURAL != type) { - onGroup?.let { onGroup -> - queryBuilder.append("ON") - .append(" ") - .append(onGroup.query) - .append(" ") - } ?: if (!using.isEmpty()) { - queryBuilder.append("USING (") - .appendList(using) - .append(") ") + private final List> using = new ArrayList<>(); + + @Override// natural joins do no have on or using clauses. + public String getQuery() { + StringBuilder queryBuilder = new StringBuilder(); + + queryBuilder.append(type.name().replace("_", " ")).append(" "); + + queryBuilder.append("JOIN") + .append(" ") + .append(alias.fullQuery()) + .append(" "); + if (JoinType.NATURAL != type) { + if(onGroup != null){ + queryBuilder.append("ON") + .append(" ") + .append(onGroup.getQuery()) + .append(" "); + }else { + if(!using.isEmpty()){ + queryBuilder.append("USING ("); + StringUtils.appendList(queryBuilder, using); + queryBuilder.append(") "); } } - return queryBuilder.toString() } + return queryBuilder.toString(); + } /** * The specific type of JOIN that is used. */ - enum class JoinType { + enum JoinType { /** * an extension of the INNER JOIN. Though SQL standard defines three types of OUTER JOINs: LEFT, RIGHT, @@ -103,19 +108,18 @@ class Join : Query { NATURAL } - constructor(from: From, table: Class, joinType: JoinType) { - this.from = from - this.table = table - type = joinType - alias = NameAlias.Builder(FlowManager.getTableName(table)).build() + public Join(From from, Class table, JoinType joinType) { + this.from = from; + this.table = table; + type = joinType; + alias = new NameAlias.Builder(FlowManager.getTableName(table)).build(); } - constructor(from: From, joinType: JoinType, - modelQueriable: ModelQueriable) { - table = modelQueriable.table - this.from = from - type = joinType - alias = PropertyFactory.from(modelQueriable).nameAlias + public Join(From from, JoinType joinType, ModelQueriable modelQueriable) { + table = modelQueriable.table(); + this.from = from; + type = joinType; + alias = PropertyFactory.from(modelQueriable).nameAlias(); } /** @@ -124,11 +128,12 @@ class Join : Query { * @param alias The name to give it * @return This instance */ - fun `as`(alias: String) = apply { + public Join as(String alias) { this.alias = this.alias .newBuilder() - .`as`(alias) - .build() + .as(alias) + .build(); + return this; } /** @@ -137,10 +142,10 @@ class Join : Query { * @param onConditions The conditions it is on * @return The FROM that this JOIN came from */ - fun on(vararg onConditions: SQLOperator): From { - checkNatural() - onGroup = OperatorGroup.nonGroupingClause().andAll(*onConditions) - return from + public From on(SQLOperator... onConditions) { + checkNatural(); + onGroup = OperatorGroup.nonGroupingClause().andAll(onConditions); + return from; } /** @@ -149,10 +154,10 @@ class Join : Query { * @param sqlOperator The operator that the JOIN is ON. * @return The FROM that this JOIN came from */ - infix fun on(sqlOperator: SQLOperator): From { - checkNatural() - onGroup = OperatorGroup.nonGroupingClause().and(sqlOperator) - return from + public From on(SQLOperator sqlOperator) { + checkNatural(); + onGroup = OperatorGroup.nonGroupingClause().and(sqlOperator); + return from; } /** @@ -161,10 +166,10 @@ class Join : Query { * @param columns THe columns to use * @return The FROM that this JOIN came from */ - fun using(vararg columns: IProperty<*>): From { - checkNatural() - using.addAll(columns) - return from + public From using(IProperty... columns) { + checkNatural(); + using.addAll(Arrays.asList(columns)); + return from; } /** @@ -173,21 +178,23 @@ class Join : Query { * @param property The property its using (singular). * @return The FROM that this JOIN came from */ - infix fun using(property: IProperty<*>): From { - checkNatural() - using.add(property) - return from + public From using(IProperty property) { + checkNatural(); + using.add(property); + return from; } /** * @return End this [Join]. Used for [Join.JoinType.NATURAL] */ - fun end(): From = from + public From end() { + return from; + } - private fun checkNatural() { + private void checkNatural() { if (JoinType.NATURAL == type) { - throw IllegalArgumentException("Cannot specify a clause for this join if its NATURAL." - + " Specifying a clause would have no effect. Call end() to continue the query.") + throw new IllegalArgumentException("Cannot specify a clause for this join if its NATURAL." + + " Specifying a clause would have no effect. Call end() to continue the query."); } } } diff --git a/lib/src/main/kotlin/com/dbflow5/query/LoadFromCursorListener.kt b/lib/src/main/java/com/dbflow5/query/LoadFromCursorListener.java similarity index 57% rename from lib/src/main/kotlin/com/dbflow5/query/LoadFromCursorListener.kt rename to lib/src/main/java/com/dbflow5/query/LoadFromCursorListener.java index 295dcc900d68bc62d0ccb43fcf1c3e5693c4c194..ec41fb12b006c47d281d51ccb69c34bcb744e707 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/LoadFromCursorListener.kt +++ b/lib/src/main/java/com/dbflow5/query/LoadFromCursorListener.java @@ -1,18 +1,18 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.database.FlowCursor -import com.dbflow5.structure.Model +import com.dbflow5.database.FlowCursor; +import com.dbflow5.structure.Model; /** * Description: Marks a [Model] as listening to [FlowCursor] * events for custom handling when loading from the DB. */ -interface LoadFromCursorListener { +public interface LoadFromCursorListener { /** * Called when the [Model] is loaded from the DB. * * @param cursor The cursor that is loaded. */ - fun onLoadFromCursor(cursor: FlowCursor) + void onLoadFromCursor(FlowCursor cursor); } diff --git a/lib/src/main/java/com/dbflow5/query/Method.java b/lib/src/main/java/com/dbflow5/query/Method.java new file mode 100644 index 0000000000000000000000000000000000000000..5a6d38fc0d1fecdf5a6ef739d333bdd4e585da01 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/Method.java @@ -0,0 +1,340 @@ +package com.dbflow5.query; + + +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.query.property.PropertyFactory; +import com.dbflow5.sql.SQLiteType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents SQLite methods on columns. These act as [Property] so we can use them in complex + * scenarios. + */ +public class Method extends Property { + + private final List> propertyList = new ArrayList<>(); + private final List operationsList = new ArrayList<>(); + private IProperty methodProperty; + private NameAlias _nameAlias = null; + + public Method(String methodName, IProperty... properties) { + super(null, ""); + + methodProperty = new Property<>(null, NameAlias.rawBuilder(methodName).build()); + + if (properties == null || properties.length == 0) { + propertyList.add(Property.ALL_PROPERTY); + } else { + for(IProperty property : properties) { + addProperty(property); + } + } + } + + public Method(IProperty... properties) { + this("", properties); + } + + @Override + public Method plus(IProperty property) { + return append(property, " "+Operator.Operation.PLUS); + } + + @Override + public Method minus(IProperty property) { + return append(property, " "+Operator.Operation.MINUS); + } + + @Override + public Property div(IProperty property) { + return append(property, " "+Operator.Operation.DIVISION); + } + + @Override + public Property times(IProperty property) { + return append(property, " "+Operator.Operation.MULTIPLY); + } + + @Override + public Property rem(IProperty property) { + return append(property, " "+Operator.Operation.MOD); + } + + /** + * Allows adding a property to the [Method]. Will remove the [Property.ALL_PROPERTY] + * if it exists as first item. + * + * @param property The property to add. + */ + public Method addProperty(IProperty property) { + return append(property, ","); + } + + /** + * Appends a property with the specified operation that separates it. The operation will appear before + * the property specified. + */ + public Method append(IProperty property, String operation) { + // remove all property since its not needed when we specify a property. + if (propertyList.size() == 1 && propertyList.get(0) == Property.ALL_PROPERTY) { + propertyList.remove(0); + } + propertyList.add(property); + operationsList.add(operation); + return this; + } + + protected List> getPropertyList() { + return propertyList; + } + + @Override + public NameAlias nameAlias() { + NameAlias alias = _nameAlias; + if (alias == null) { + StringBuilder query = new StringBuilder(methodProperty.getQuery() != null? methodProperty.getQuery() : ""); + query.append("("); + List> propertyList = getPropertyList(); + for (int i=0;i property = propertyList.get(i); + if (i > 0) { + query.append(operationsList.get(i)); + query.append(" "); + } + query.append(property.toString()); + } + query.append(")"); + alias = NameAlias.rawBuilder(query.toString()).build(); + } + this._nameAlias = alias; + return alias; + } + + /** + * Represents the SQLite CAST operator. + */ + public static class Cast{ + private IProperty property; + + Cast(IProperty property) { + this.property = property; + } + + /** + * @param sqLiteType The type of column to cast it to. + * @return A new [Method] that represents the statement. + */ + public Property as(SQLiteType sqLiteType) { + Property newProperty = new Property<>(property.table(), + property.nameAlias() + .newBuilder() + .shouldAddIdentifierToAliasName(false) + .as(sqLiteType.name()) + .build()); + return new Method("CAST", newProperty); + } + + /** + * Returns a [Property] of [Int] so it can accept [Int] values. + */ + public Property asInteger() { + return (Property)(as(SQLiteType.INTEGER)); + } + + /** + * Returns a [Property] of [Double] so it can accept [Double] values. + */ + public Property asReal() { + return (Property)as(SQLiteType.REAL); + } + + /** + * Returns a [Property] of [String] so it can accept [String] values. + */ + public Property asText() { + return (Property)as(SQLiteType.TEXT); + } + } + + /** + * @param properties Set of properties that the method acts on. + * @return The average value of all properties within this group. The result is always a float from this statement + * as long as there is at least one non-NULL input. The result may be NULL if there are no non-NULL columns. + */ + public static Method avg(IProperty... properties) { + return new Method("AVG", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return A count of the number of times that specified properties are not NULL in a group. Leaving + * the properties empty returns COUNT(*), which is the total number of rows in the query. + */ + public static Method count(IProperty... properties) { + return new Method("COUNT", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return A string which is the concatenation of all non-NULL values of the properties. + */ + public static Method groupConcat(IProperty... properties) { + return new Method("GROUP_CONCAT", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return The method that represents the max of the specified columns/properties. + */ + public static Method max(IProperty... properties) { + return new Method("MAX", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return The method that represents the min of the specified columns/properties. + */ + public static Method min(IProperty... properties) { + return new Method("MIN", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return The method that represents the sum of the specified columns/properties. + */ + public static Method sum(IProperty... properties) { + return new Method("SUM", properties); + } + + /** + * @param properties Set of properties that the method acts on. + * @return The method that represents the total of the specified columns/properties. + */ + public static Method total(IProperty... properties) { + return new Method("TOTAL", properties); + } + + /** + * @param property The property to cast. + * @return A new CAST object. To complete use the [Cast. as] method. + */ + public static Method.Cast cast(IProperty property) { + return new Cast(property); + } + + public static Method replace(IProperty property, String findString, String replacement) { + return new Method("REPLACE", property, PropertyFactory.from(findString), PropertyFactory.from(replacement)); + } + + /** + * SQLite standard "strftime()" method. See SQLite documentation on this method. + */ + public static Method strftime(String formatString, String timeString, String... modifiers) { + List> propertyList = new ArrayList<>(); + propertyList.add(PropertyFactory.from(formatString)); + propertyList.add(PropertyFactory.from(timeString)); + for(String modifier : modifiers) { + propertyList.add(PropertyFactory.from(modifier)); + } + return new Method("strftime", propertyList.toArray(new IProperty[]{})); + } + + /** + * Sqlite "datetime" method. See SQLite documentation on this method. + */ + public static Method datetime(long timeStamp, String... modifiers) { + List> propertyList = new ArrayList<>(); + propertyList.add(PropertyFactory.from(timeStamp)); + for(String modifier : modifiers) { + propertyList.add(PropertyFactory.from(modifier)); + } + return new Method("datetime", propertyList.toArray(new IProperty[]{})); + } + + /** + * Sqlite "date" method. See SQLite documentation on this method. + */ + public static Method date(String timeString, String... modifiers) { + List> propertyList = new ArrayList<>(); + propertyList.add(PropertyFactory.from(timeString)); + for(String modifier : modifiers) { + propertyList.add(PropertyFactory.from(modifier)); + } + return new Method("date", propertyList.toArray(new IProperty[]{})); + } + + /** + * @return Constructs using the "IFNULL" method in SQLite. It will pick the first non null + * value and set that. If both are NULL then it will use NULL. + */ + public static Method ifNull(IProperty first, IProperty secondIfFirstNull) { + return new Method("IFNULL", first, secondIfFirstNull); + } + + /** + * @return Constructs using the "NULLIF" method in SQLite. If both expressions are equal, then + * NULL is set into the DB. + */ + public static Method nullIf(IProperty first, IProperty second) { + return new Method("NULLIF", first, second); + } + + public static Method random() { + return new Method("RANDOM", Property.NO_PROPERTY); + } + + /** + * Used for FTS: + * + * For a SELECT query that uses the full-text index, the offsets() function returns a + * text value containing a series of space-separated integers. For each term in each phrase + * match of the current row, there are four integers in the returned list. + * Each set of four integers is interpreted as follows: + * Integer Interpretation + * 0 The column number that the term instance occurs in (0 for the leftmost column of the FTS table, 1 for the next leftmost, etc.). + * 1 The term number of the matching term within the full-text query expression. Terms within a query expression are numbered starting from 0 in the order that they occur. + * 2 The byte offset of the matching term within the column. + * 3 The size of the matching term in bytes. + * + * For more see sqlite.org + */ + public static Method offsets(Class table) { + return new Method("offsets", PropertyFactory.tableName(table)); + } + + /** + * Used for FTS: + * The snippet function is used to create formatted fragments of document text for + * display as part of a full-text query results report. + * + * @param table table + * @param start - the start match text. + * @param end - the end match text + * @param ellipses ellipses + * @param index - The FTS table column number to extract the returned fragments of + * text from. Columns are numbered from left to right starting with zero. + * A negative value indicates that the text may be extracted from any column. + * @param approximateTokens - The absolute value of this integer argument is used as the + * (approximate) number of tokens to include in the returned text value. + * The maximum allowable absolute value is 64. + * @return Method + * For more see sqlite.org + */ + public static Method snippet(Class table, String start, String end, String ellipses, int index, int approximateTokens) { + List list = Arrays.asList(PropertyFactory.tableName(table), start, end, ellipses, index, approximateTokens); + List> propertyList = new ArrayList<>(); + for(Object obj : list) { + if(obj instanceof String){ + propertyList.add(PropertyFactory.propertyString(table, "'"+obj+"'")); + }else { + propertyList.add(PropertyFactory.propertyString(table, obj.toString())); + } + } + return new Method("snippet", (Property[]) propertyList.toArray()); + } +} + diff --git a/lib/src/main/kotlin/com/dbflow5/query/ModelQueriable.kt b/lib/src/main/java/com/dbflow5/query/ModelQueriable.java similarity index 38% rename from lib/src/main/kotlin/com/dbflow5/query/ModelQueriable.kt rename to lib/src/main/java/com/dbflow5/query/ModelQueriable.java index 7ddb5c6699249549161171dfc6168210b826c350..b15a3afd353a25a028b424dd26f5d574a1408ddf 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/ModelQueriable.kt +++ b/lib/src/main/java/com/dbflow5/query/ModelQueriable.java @@ -1,50 +1,59 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.SQLiteException -import com.dbflow5.query.list.FlowCursorList -import com.dbflow5.query.list.FlowQueryList +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.SQLiteException; +import com.dbflow5.query.list.FlowCursorList; +import com.dbflow5.query.list.FlowQueryList; +import com.dbflow5.transaction.Transaction; + +import java.util.List; +import java.util.function.Function; /** * Description: An interface for query objects to enable you to cursor from the database in a structured way. * Examples of such statements are: [From], [Where], [StringQuery] */ -interface ModelQueriable : Queriable { +public interface ModelQueriable extends Queriable { /** * @return the table that this query comes from. */ - val table: Class + Class table(); /** * @return a list of model converted items */ - fun queryList(databaseWrapper: DatabaseWrapper): MutableList + List queryList(DatabaseWrapper databaseWrapper); /** * @return Single model, the first of potentially many results */ - fun querySingle(databaseWrapper: DatabaseWrapper): T? + T querySingle(DatabaseWrapper databaseWrapper); /** * A non-null result. throws a [SQLiteException] if the query reaches no result. */ - fun requireSingle(db: DatabaseWrapper) = querySingle(db) - ?: throw SQLiteException("Model result not found for $this") + default T requireSingle(DatabaseWrapper db){ + T t = querySingle(db); + if(t != null){ + return t; + } + throw new SQLiteException("Model result not found for " + this); + } /** * @return A cursor-backed list that handles conversion, retrieval, and caching of lists. Can * cache models dynamically by setting [FlowCursorList.setCacheModels] to true. */ - fun cursorList(databaseWrapper: DatabaseWrapper): FlowCursorList + FlowCursorList cursorList(DatabaseWrapper databaseWrapper); /** * @return A cursor-backed [List] that handles conversion, retrieval, caching, content changes, * and more. */ - fun flowQueryList(databaseWrapper: DatabaseWrapper): FlowQueryList + FlowQueryList flowQueryList(DatabaseWrapper databaseWrapper); /** * Returns a [List] based on the custom [TQuery] you pass in. @@ -52,8 +61,7 @@ interface ModelQueriable : Queriable { * @param queryModelClass The query model class to use. * @return A list of custom models that are not tied to a table. */ - fun queryCustomList(queryModelClass: Class, - databaseWrapper: DatabaseWrapper): MutableList + // List queryCustomList(Class queryModelClass, DatabaseWrapper databaseWrapper); /** * Returns a single [TQueryModel] from this query. @@ -61,51 +69,69 @@ interface ModelQueriable : Queriable { * @param queryModelClass The class to use. * @return A single model from the query. */ - fun queryCustomSingle(queryModelClass: Class, - databaseWrapper: DatabaseWrapper): TQueryModel? + // TQueryModel queryCustomSingle(Class queryModelClass, DatabaseWrapper databaseWrapper); /** * Disables caching on this query for the object retrieved from DB (if caching enabled). If * caching is not enabled, this method is ignored. This also disables caching in a [FlowCursorList] * or [FlowQueryList] if you [.flowQueryList] or [.cursorList] */ - fun disableCaching(): ModelQueriable + ModelQueriable disableCaching(); /** * Begins an async DB transaction using the specified TransactionManager. */ - fun async(databaseWrapper: DBFlowDatabase, - modelQueriableFn: ModelQueriable.(DatabaseWrapper) -> R) = - databaseWrapper.beginTransactionAsync { modelQueriableFn(it) } + //modelQueriableFn: ModelQueriable.(DatabaseWrapper) -> R + default Transaction.Builder async(DBFlowDatabase databaseWrapper, Function modelQueriableFn) { + return databaseWrapper.beginTransactionAsync(modelQueriableFn); + } /** * Attempt to constrain this [ModelQueriable] if it supports it via [Transformable] methods. Otherwise, * we just return itself. */ - @Suppress("UNCHECKED_CAST") - fun attemptConstrain(offset: Long, limit: Long): ModelQueriable { - return when { - this is Transformable<*> -> (this as Transformable).constrain(offset, limit) - else -> this + default ModelQueriable attemptConstrain(long offset, long limit) { + if(this instanceof Transformable){ + return ((Transformable)this).constrain(offset, limit); } + return this; } -} + /** + * Trims and wraps a [ModelQueriable.query] in parenthesis. + * E.G. wraps: select * from table into (select * from table) + */ + default String enclosedQuery() { + return getQuery().trim(); + } -/** - * Trims and wraps a [ModelQueriable.query] in parenthesis. - * E.G. wraps: select * from table into (select * from table) - */ -internal inline val ModelQueriable.enclosedQuery - get() = "(${query.trim { it <= ' ' }})" + default List queryCustomList(Class clazz, DatabaseWrapper db) { + return queryCustomList(clazz, db); + } -inline fun ModelQueriable.queryCustomList(db: DatabaseWrapper) = - queryCustomList(T::class.java, db) + default V queryCustomSingle(Class clazz, DatabaseWrapper db) { + return queryCustomSingle(clazz, db); + } -inline fun ModelQueriable.queryCustomSingle(db: DatabaseWrapper) = - queryCustomSingle(T::class.java, db) + default V requireCustomSingle(Class clazz, DatabaseWrapper db) { + V v = queryCustomSingle(clazz, db); + if(v == null){ + throw new SQLiteException("QueryModel result not found for "+ this); + } + return v; + } -inline fun ModelQueriable.requireCustomSingle(db: DatabaseWrapper) = - queryCustomSingle(T::class.java, db) - ?: throw SQLiteException("QueryModel result not found for $this") \ No newline at end of file + /** + * Extracts the [From] from a [ModelQueriable] if possible to get [From.associatedTables] + */ + default From extractFrom() { + if (this instanceof From) { + return (From)this; + } else if (this instanceof Where && ((Where)this).whereBase instanceof From) { + return ((From)(((Where)this)).whereBase); + } else { + return null; + } + } +} diff --git a/lib/src/main/java/com/dbflow5/query/NameAlias.java b/lib/src/main/java/com/dbflow5/query/NameAlias.java new file mode 100644 index 0000000000000000000000000000000000000000..efda298d4b2c9c5ec59deccfd0478ef50f95703a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/NameAlias.java @@ -0,0 +1,297 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.sql.Query; + +/** + * Description: Rewritten from the ground up, this class makes it easier to build an alias. + */ +public class NameAlias implements Query { + private final String name; + private final String aliasName; + public String tableName = null; + public String keyword = null; + public boolean shouldStripIdentifier = true; + public boolean shouldStripAliasName = true; + private boolean shouldAddIdentifierToQuery = true; + private boolean shouldAddIdentifierToAliasName = true; + + public NameAlias(String name, + String aliasName, + String tableName, + String keyword, + boolean shouldStripIdentifier, + boolean shouldStripAliasName, + boolean shouldAddIdentifierToQuery, + boolean shouldAddIdentifierToAliasName){ + + this.name = name; + this.aliasName = aliasName; + this.tableName = tableName; + this.keyword = keyword; + this.shouldStripIdentifier = shouldStripIdentifier; + this.shouldStripAliasName = shouldStripAliasName; + this.shouldAddIdentifierToQuery = shouldAddIdentifierToQuery; + this.shouldAddIdentifierToAliasName = shouldAddIdentifierToAliasName; + } + + /** + * @return The name used in queries. If an alias is specified, use that, otherwise use the name + * of the property with a table name (if specified). + */ + public String getQuery(){ + if(StringUtils.isNotNullOrEmpty(aliasName)){ + return aliasName; + }else if(StringUtils.isNotNullOrEmpty(name)){ + return fullName(); + }else { + return ""; + } + } + + /** + * @return The value used as a key. Uses either the [.aliasNameRaw] + * or the [.nameRaw], depending on what's specified. + */ + public String nameAsKey() { + if (StringUtils.isNotNullOrEmpty(aliasName)) { + return aliasNameRaw(); + } else { + return nameRaw(); + } + } + + /** + * @return The full query that represents itself with `{tableName}`.`{name}` AS `{aliasName}` + */ + public String fullQuery() { + String query = fullName(); + if (StringUtils.isNotNullOrEmpty(aliasName)) { + query += " AS "+aliasName(); + } + if (StringUtils.isNotNullOrEmpty(keyword)) { + query = keyword+ " "+ query; + } + return query; + } + + private NameAlias(Builder builder) { + this(builder.shouldStripIdentifier? StringUtils.stripQuotes(builder.name) == null? "" : StringUtils.stripQuotes(builder.name) : builder.name, + builder.shouldStripAliasName? StringUtils.stripQuotes(builder.aliasName): builder.aliasName, + StringUtils.isNotNullOrEmpty(builder.tableName)? StringUtils.quoteIfNeeded(builder.tableName) : null, + builder.keyword, + builder.shouldStripIdentifier, + builder.shouldStripAliasName, + builder.shouldAddIdentifierToQuery, + builder.shouldAddIdentifierToAliasName); + } + + /** + * @return The real column name. + */ + public String name() { + if (StringUtils.isNotNullOrEmpty(name) && shouldAddIdentifierToQuery) + return StringUtils.quoteIfNeeded(name); + else return name; + } + + /** + * @return The name, stripped from identifier syntax completely. + */ + public String nameRaw() { + return shouldStripIdentifier? name : StringUtils.stripQuotes(name) == null? "" : StringUtils.stripQuotes(name); + } + + /** + * @return The name used as part of the AS query. + */ + public String aliasName() { + if (StringUtils.isNotNullOrEmpty(aliasName) && shouldAddIdentifierToAliasName) + return StringUtils.quoteIfNeeded(aliasName); + else return aliasName; + } + + /** + * @return The alias name, stripped from identifier syntax completely. + */ + public String aliasNameRaw() { + return shouldStripAliasName? aliasName : StringUtils.stripQuotes(aliasName); + } + + /** + * @return The `{tableName}`.`{name}`. If [.tableName] specified. + */ + public String fullName() { + System.out.println("tableName:"+tableName); + return (StringUtils.isNotNullOrEmpty(tableName)? tableName + "." : "") + name(); + } + + @Override + public java.lang.String toString() { + return fullQuery(); + } + + /** + * @return Constructs a builder as a new instance that can be modified without fear. + */ + public Builder newBuilder() { + return new Builder(name) + .keyword(keyword) + .as(aliasName) + .shouldStripAliasName(shouldStripAliasName) + .shouldStripIdentifier(shouldStripIdentifier) + .shouldAddIdentifierToName(shouldAddIdentifierToQuery) + .shouldAddIdentifierToAliasName(shouldAddIdentifierToAliasName) + .withTable(tableName); + } + + public Operator op() { + return Operator.op(this); + } + + public static class Builder { + String name; + String aliasName = null; + String tableName = null; + boolean shouldStripIdentifier = true; + boolean shouldStripAliasName = true; + boolean shouldAddIdentifierToQuery = true; + boolean shouldAddIdentifierToAliasName = true; + String keyword = null; + + public Builder(String name){ + this.name = name; + } + + /** + * Appends a DISTINCT that prefixes this alias class. + */ + public Builder distinct() { + return keyword("DISTINCT"); + } + + /** + * Appends a keyword that prefixes this alias class. + */ + public Builder keyword(String keyword) { + this.keyword = keyword; + return this; + } + + /** + * Provide an alias that is used `{name}` AS `{aliasName}` + */ + public Builder as(String aliasName) { + this.aliasName = aliasName; + return this; + } + + /** + * Provide a table-name prefix as such: `{tableName}`.`{name}` + */ + public Builder withTable(String tableName) { + this.tableName = tableName; + return this; + } + + /** + * @param shouldStripIdentifier If true, we normalize the identifier [.name] from any + * ticks around the name. If false, we leave it as such. + */ + public Builder shouldStripIdentifier(boolean shouldStripIdentifier) { + this.shouldStripIdentifier = shouldStripIdentifier; + return this; + } + + /** + * @param shouldStripAliasName If true, we normalize the identifier [.aliasName] from any + * ticks around the name. If false, we leave it as such. + */ + public Builder shouldStripAliasName(boolean shouldStripAliasName) { + this.shouldStripAliasName = shouldStripAliasName; + return this; + } + + /** + * @param shouldAddIdentifierToName If true (default), we add the identifier to the name: `{name}` + */ + public Builder shouldAddIdentifierToName(boolean shouldAddIdentifierToName) { + this.shouldAddIdentifierToQuery = shouldAddIdentifierToName; + return this; + } + + /** + * @param shouldAddIdentifierToAliasName If true (default), we add an identifier to the alias + * name. `{aliasName}` + */ + public Builder shouldAddIdentifierToAliasName(boolean shouldAddIdentifierToAliasName) { + this.shouldAddIdentifierToAliasName = shouldAddIdentifierToAliasName; + return this; + } + + public NameAlias build() { + return new NameAlias(this); + } + + } + + /** + * Combines any number of names into a single [NameAlias] separated by some operation. + * + * @param operation The operation to separate into. + * @param names The names to join. + * @return The new namealias object. + */ + public static NameAlias joinNames(String operation, String... names) { + StringBuilder newName = new StringBuilder(); + for (int i=0;i< names.length;i++) { + if (i > 0) { + newName.append(" ").append(operation).append(" "); + } + newName.append(names[i]); + } + return rawBuilder(newName.toString()).build(); + } + + public static Builder builder(String name) { + return new Builder(name); + } + + public static Builder tableNameBuilder(String tableName) { + return new Builder("") + .withTable(tableName); + } + + /** + * @param name The raw name of this alias. + * @return A new instance without adding identifier `` to any part of the query. + */ + public static Builder rawBuilder(String name) { + return new Builder(name) + .shouldStripIdentifier(false) + .shouldAddIdentifierToName(false); + } + + public static NameAlias of(String name) { + return builder(name).build(); + } + + public static NameAlias of(String name, String aliasName) { + return builder(name).as(aliasName).build(); + } + + public static NameAlias ofTable(String tableName, String name) { + return builder(name).withTable(tableName).build(); + } + + public static NameAlias nameAlias(String name) { + return NameAlias.of(name); + } + + public static NameAlias as(String name, String alias) { + return NameAlias.of(name, alias); + } + +} + + diff --git a/lib/src/main/kotlin/com/dbflow5/query/Operator.kt b/lib/src/main/java/com/dbflow5/query/Operator.java similarity index 30% rename from lib/src/main/kotlin/com/dbflow5/query/Operator.kt rename to lib/src/main/java/com/dbflow5/query/Operator.java index ac0fc0408f8088b94aba83c4f32748d67619e239..3e7303225ac500842fc09f75f6f9deef6fa7e5d2 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Operator.kt +++ b/lib/src/main/java/com/dbflow5/query/Operator.java @@ -1,73 +1,104 @@ -@file:Suppress("UNCHECKED_CAST") +package com.dbflow5.query; -package com.dbflow5.query +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.Collate; +import com.dbflow5.config.FlowLog; +import com.dbflow5.config.FlowManager; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.query.property.TypeConvertedProperty; +import com.dbflow5.sql.Query; -import com.dbflow5.annotation.Collate -import com.dbflow5.appendOptional -import com.dbflow5.config.FlowLog -import com.dbflow5.config.FlowManager -import com.dbflow5.converter.TypeConverter -import com.dbflow5.query.property.Property -import com.dbflow5.query.property.TypeConvertedProperty -import com.dbflow5.sql.Query +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; /** * Description: The class that contains a column name, Operator, and value. * This class is mostly reserved for internal use at this point. Using this class directly should be avoided * and use the generated [Property] instead. */ -class Operator -internal constructor(nameAlias: NameAlias?, - private val table: Class<*>? = null, - private val getter: TypeConvertedProperty.TypeConverterGetter? = null, - private val convertToDB: Boolean) : BaseOperator(nameAlias), IOperator { +public class Operator extends BaseOperator implements IOperator { - private val typeConverter: TypeConverter<*, *>? by lazy { table?.let { table -> getter?.getTypeConverter(table) } } + NameAlias nameAlias; + private final Class table; + private final TypeConvertedProperty.TypeConverterGetter getter; + private final boolean convertToDB; - private var convertToString = true + private TypeConverters.TypeConverter typeConverter = null; - override val query: String - get() = appendToQuery() + public Operator(NameAlias nameAlias, Class table, TypeConvertedProperty.TypeConverterGetter getter, boolean convertToDB){ + super(nameAlias); + this.table = table; + this.getter = getter; + this.convertToDB = convertToDB; + if(table != null && getter != null) { + typeConverter = getter.getTypeConverter(table); + } + } + + private boolean convertToString = true; - internal constructor(operator: Operator<*>) - : this(operator.nameAlias, operator.table, operator.getter, operator.convertToDB) { - this.value = operator.value + @Override + public String getQuery() { + return appendToQuery(); } - override fun appendConditionToQuery(queryBuilder: StringBuilder) { - queryBuilder.append(columnName()).append(operation()) + public Operator(Operator operator) { + this(operator.nameAlias, operator.table, operator.getter, operator.convertToDB); + this.value = operator.value; + } + + @Override + public void appendConditionToQuery(StringBuilder queryBuilder) { + queryBuilder.append(columnName()).append(operation()); // Do not use value for certain operators // If is raw, we do not want to convert the value to a string. if (isValueSet) { - queryBuilder.append(if (convertToString) convertObjectToString(value(), true) else value()) + queryBuilder.append(convertToString? convertObjectToString(value(), true) : value()); } - postArgument()?.let { queryBuilder.append(" $it") } + if(postArgument() != null){ + queryBuilder.append(" "); + queryBuilder.append(postArgument()); + } } - override fun isNull() = apply { - operation = " ${Operation.IS_NULL} " + @Override + public Operator isNull() { + operation = " "+Operation.IS_NULL+" "; + return this; } - override fun isNotNull() = apply { - operation = " ${Operation.IS_NOT_NULL} " + @Override + public Operator isNotNull() { + operation = " "+Operation.IS_NOT_NULL+" "; + return this; } - override fun `is`(value: T?): Operator { - operation = Operation.EQUALS - return value(value) + @Override + public Operator is(T value) { + operation = Operation.EQUALS; + return value(value); } - override fun eq(value: T?): Operator = `is`(value) + @Override + public Operator eq(T value) { + return is(value); + } - override fun isNot(value: T?): Operator { - operation = Operation.NOT_EQUALS - return value(value) + @Override + public Operator isNot(T value) { + operation = Operation.NOT_EQUALS; + return value(value); } - override fun notEq(value: T?): Operator = isNot(value) + @Override + public Operator notEq(T value) { + return isNot(value); + } /** * Uses the LIKE operation. Case insensitive comparisons. @@ -79,9 +110,10 @@ internal constructor(nameAlias: NameAlias?, * The _ represents a single number or character. * @return This condition */ - override fun like(value: String): Operator { - operation = " ${Operation.LIKE} " - return value(value) + @Override + public Operator like(String value) { + operation = " "+Operation.LIKE+" "; + return value(value); } @@ -95,9 +127,10 @@ internal constructor(nameAlias: NameAlias?, * @param value a simple string to match. */ - override fun match(value: String): Operator { - operation = " ${Operation.MATCH} " - return value(value) + @Override + public Operator match(String value) { + operation = " "+Operation.MATCH+" "; + return value(value); } /** @@ -110,9 +143,10 @@ internal constructor(nameAlias: NameAlias?, * The _ represents a single number or character. * */ - override fun notLike(value: String): Operator { - operation = " ${Operation.NOT_LIKE} " - return value(value) + @Override + public Operator notLike(String value) { + operation = " "+Operation.NOT_LIKE+" "; + return value(value); } /** @@ -125,9 +159,10 @@ internal constructor(nameAlias: NameAlias?, * The ? represents a single number or character * */ - override fun glob(value: String): Operator { - operation = " ${Operation.GLOB} " - return value(value) + @Override + public Operator glob(String value) { + operation = " "+Operation.GLOB+" "; + return value(value); } /** @@ -136,40 +171,60 @@ internal constructor(nameAlias: NameAlias?, * @param value The value of the column in the DB * */ - fun value(value: Any?) = apply { - this.value = value - isValueSet = true + public Operator value(Object value) { + this.value = value; + isValueSet = true; + return this; } - override fun greaterThan(value: T): Operator { - operation = Operation.GREATER_THAN - return value(value) + @Override + public Operator greaterThan(T value) { + operation = Operation.GREATER_THAN; + return value(value); } - override fun greaterThanOrEq(value: T): Operator { - operation = Operation.GREATER_THAN_OR_EQUALS - return value(value) + @Override + public Operator greaterThanOrEq(T value) { + operation = Operation.GREATER_THAN_OR_EQUALS; + return value(value); } - override fun lessThan(value: T): Operator { - operation = Operation.LESS_THAN - return value(value) + @Override + public Operator lessThan(T value) { + operation = Operation.LESS_THAN; + return value(value); } - override fun lessThanOrEq(value: T): Operator { - operation = Operation.LESS_THAN_OR_EQUALS - return value(value) + @Override + public Operator lessThanOrEq(T value) { + operation = Operation.LESS_THAN_OR_EQUALS; + return value(value); } - override fun plus(value: T): Operator = assignValueOp(value, Operation.PLUS) + @Override + public Operator plus(T value) { + return assignValueOp(value, Operation.PLUS); + } - override fun minus(value: T): Operator = assignValueOp(value, Operation.MINUS) + @Override + public Operator minus(T value) { + return assignValueOp(value, Operation.MINUS); + } - override fun div(value: T): Operator = assignValueOp(value, Operation.DIVISION) + @Override + public Operator div(T value) { + return assignValueOp(value, Operation.DIVISION); + } - override fun times(value: T): Operator = assignValueOp(value, Operation.MULTIPLY) + @Override + public Operator times(T value) { + return assignValueOp(value, Operation.MULTIPLY); + } - override fun rem(value: T): Operator = assignValueOp(value, Operation.MOD) + @Override + public Operator rem(T value) { + return assignValueOp(value, Operation.MOD); + } /** * Add a custom operation to this argument @@ -177,8 +232,9 @@ internal constructor(nameAlias: NameAlias?, * @param operation The SQLite operator * */ - fun operation(operation: String) = apply { - this.operation = operation + public Operator operation(String operation) { + this.operation = operation; + return this; } /** @@ -187,8 +243,9 @@ internal constructor(nameAlias: NameAlias?, * @param collation The SQLite collate function * . */ - infix fun collate(collation: String) = apply { - postArg = "COLLATE $collation" + public Operator collate(String collation) { + postArg = "COLLATE " + collation; + return this; } /** @@ -197,19 +254,21 @@ internal constructor(nameAlias: NameAlias?, * @param collation The SQLite collate function * . */ - infix fun collate(collation: Collate) = apply { + public Operator collate(Collate collation) { if (collation == Collate.NONE) { - postArg = null + postArg = null; } else { - collate(collation.name) + collate(collation.name()); } + return this; } /** * Appends an optional SQL string to the end of this condition */ - infix fun postfix(postfix: String) = apply { - postArg = postfix + public Operator postfix(String postfix) { + postArg = postfix; + return this; } /** @@ -218,141 +277,228 @@ internal constructor(nameAlias: NameAlias?, * @param separator The separator to use * @return This instance */ - override fun separator(separator: String) = apply { - this.separator = separator + @Override + public Operator separator(String separator) { + this.separator = separator; + return this; } - override fun `is`(conditional: IConditional): Operator<*> = - assignValueOp(conditional, Operation.EQUALS) - - override fun eq(conditional: IConditional): Operator<*> = - assignValueOp(conditional, Operation.EQUALS) - - override fun isNot(conditional: IConditional): Operator<*> = - assignValueOp(conditional, Operation.NOT_EQUALS) - - override fun notEq(conditional: IConditional): Operator<*> = - assignValueOp(conditional, Operation.NOT_EQUALS) + @Override + public Operator is(IConditional conditional) { + return assignValueOp(conditional, Operation.EQUALS); + } - override fun like(conditional: IConditional): Operator = like(conditional.query) + @Override + public Operator eq(IConditional conditional) { + return assignValueOp(conditional, Operation.EQUALS); + } - override fun glob(conditional: IConditional): Operator = glob(conditional.query) + @Override + public Operator isNot(IConditional conditional) { + return assignValueOp(conditional, Operation.NOT_EQUALS); + } - override fun greaterThan(conditional: IConditional): Operator = - assignValueOp(conditional, Operation.GREATER_THAN) + @Override + public Operator notEq(IConditional conditional) { + return assignValueOp(conditional, Operation.NOT_EQUALS); + } - override fun greaterThanOrEq(conditional: IConditional): Operator = - assignValueOp(conditional, Operation.GREATER_THAN_OR_EQUALS) + @Override + public Operator like(IConditional conditional) { + return like(conditional.getQuery()); + } - override fun lessThan(conditional: IConditional): Operator = - assignValueOp(conditional, Operation.LESS_THAN) + @Override + public Operator glob(IConditional conditional) { + return glob(conditional.getQuery()); + } - override fun lessThanOrEq(conditional: IConditional): Operator = - assignValueOp(conditional, Operation.LESS_THAN_OR_EQUALS) + @Override + public Operator greaterThan(IConditional conditional) { + return assignValueOp(conditional, Operation.GREATER_THAN); + } - override fun between(conditional: IConditional): Between<*> = Between(this as Operator, conditional) + @Override + public Operator greaterThanOrEq(IConditional conditional) { + return assignValueOp(conditional, Operation.GREATER_THAN_OR_EQUALS); + } - override fun `in`(firstConditional: IConditional, vararg conditionals: IConditional): In<*> = - In(this as Operator, firstConditional, true, *conditionals) + @Override + public Operator lessThan(IConditional conditional) { + return assignValueOp(conditional, Operation.LESS_THAN); + } - override fun notIn(firstConditional: IConditional, vararg conditionals: IConditional): In<*> = - In(this as Operator, firstConditional, false, *conditionals) + @Override + public Operator lessThanOrEq(IConditional conditional) { + return assignValueOp(conditional, Operation.LESS_THAN_OR_EQUALS); + } - override fun notIn(firstBaseModelQueriable: BaseModelQueriable<*>, - vararg baseModelQueriables: BaseModelQueriable<*>): In<*> = - In(this as Operator, firstBaseModelQueriable, false, *baseModelQueriables) + @Override + public Between between(IConditional conditional) { + return new Between<>((Operator)this, conditional); + } - override fun `is`(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - assignValueOp(baseModelQueriable, Operation.EQUALS) + @Override + public In in(IConditional firstConditional, IConditional... conditionals) { + return new In<>((Operator)this, firstConditional, true, conditionals); + } - override fun eq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - assignValueOp(baseModelQueriable, Operation.EQUALS) + @Override + public In notIn(IConditional firstConditional, IConditional... conditionals) { + return new In<>((Operator)this, firstConditional, false, conditionals); + } - override fun isNot(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - assignValueOp(baseModelQueriable, Operation.NOT_EQUALS) + @Override + public In notIn(BaseModelQueriable firstBaseModelQueriable, BaseModelQueriable... baseModelQueriables) { + return new In<>((Operator)this, firstBaseModelQueriable, false, baseModelQueriables); + } - override fun notEq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - assignValueOp(baseModelQueriable, Operation.NOT_EQUALS) + @Override + public Operator is(BaseModelQueriable baseModelQueriable){ + return assignValueOp(baseModelQueriable, Operation.EQUALS); + } - override fun like(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.LIKE) + @Override + public Operator eq(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.EQUALS); + } - override fun notLike(conditional: IConditional): Operator<*> = - assignValueOp(conditional, Operation.NOT_LIKE) + @Override + public Operator isNot(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.NOT_EQUALS); + } - override fun notLike(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - assignValueOp(baseModelQueriable, Operation.NOT_LIKE) + @Override + public Operator notEq(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.NOT_EQUALS); + } - override fun glob(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.GLOB) + @Override + public Operator like(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.LIKE); + } - override fun greaterThan(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.GREATER_THAN) - override fun greaterThanOrEq(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.GREATER_THAN_OR_EQUALS) + @Override + public Operator notLike(IConditional conditional) { + return assignValueOp(conditional, Operation.NOT_LIKE); + } - override fun lessThan(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.LESS_THAN) + @Override + public Operator notLike(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.NOT_LIKE); + } - override fun lessThanOrEq(baseModelQueriable: BaseModelQueriable<*>): Operator = - assignValueOp(baseModelQueriable, Operation.LESS_THAN_OR_EQUALS) + @Override + public Operator glob(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.GLOB); + } + + @Override + public Operator greaterThan(BaseModelQueriable baseModelQueriable) { + return assignValueOp(baseModelQueriable, Operation.GREATER_THAN); + } + + @Override + public Operator greaterThanOrEq( BaseModelQueriable baseModelQueriable ) { + return assignValueOp(baseModelQueriable, Operation.GREATER_THAN_OR_EQUALS); + } + + @Override + public Operator lessThan( BaseModelQueriable baseModelQueriable ) { + return assignValueOp(baseModelQueriable, Operation.LESS_THAN); + } + + @Override + public Operator lessThanOrEq( BaseModelQueriable baseModelQueriable ) { + return assignValueOp(baseModelQueriable, Operation.LESS_THAN_OR_EQUALS); + } - operator fun plus(value: IConditional): Operator<*> = assignValueOp(value, Operation.PLUS) + public Operator plus(IConditional value) { + return assignValueOp(value, Operation.PLUS); + } - operator fun minus(value: IConditional): Operator<*> = assignValueOp(value, Operation.MINUS) + public Operator minus(IConditional value) { + return assignValueOp(value, Operation.MINUS); + } - operator fun div(value: IConditional): Operator<*> = assignValueOp(value, Operation.DIVISION) + public Operator div(IConditional value) { + return assignValueOp(value, Operation.DIVISION); + } - operator fun times(value: IConditional): Operator<*> = assignValueOp(value, Operation.MULTIPLY) + public Operator times(IConditional value) { + return assignValueOp(value, Operation.MULTIPLY); + } - operator fun rem(value: IConditional): Operator<*> = assignValueOp(value, Operation.MOD) + public Operator rem(IConditional value) { + return assignValueOp(value, Operation.MOD);} - override fun plus(value: BaseModelQueriable<*>): Operator<*> = - assignValueOp(value, Operation.PLUS) + public Operator plus(BaseModelQueriable value) { + return assignValueOp(value, Operation.PLUS); + } - override fun minus(value: BaseModelQueriable<*>): Operator<*> = - assignValueOp(value, Operation.MINUS) + @Override + public Operator minus(BaseModelQueriable value) { + return assignValueOp(value, Operation.MINUS); + } - override fun div(value: BaseModelQueriable<*>): Operator<*> = - assignValueOp(value, Operation.DIVISION) + @Override + public Operator div(BaseModelQueriable value) { + return assignValueOp(value, Operation.DIVISION); + } - override fun times(value: BaseModelQueriable<*>): Operator<*> = - assignValueOp(value, Operation.MULTIPLY) + @Override + public Operator times(BaseModelQueriable value) { + return assignValueOp(value, Operation.MULTIPLY); + } - override fun rem(value: BaseModelQueriable<*>): Operator<*> = - assignValueOp(value, Operation.MOD) + @Override + public Operator rem(BaseModelQueriable value) { + return assignValueOp(value, Operation.MOD); + } - override fun between(baseModelQueriable: BaseModelQueriable<*>): Between<*> = - Between(this as Operator, baseModelQueriable) + @Override + public Between between( BaseModelQueriable baseModelQueriable) { + return new Between<>((Operator)this, baseModelQueriable); + } - override fun `in`(firstBaseModelQueriable: BaseModelQueriable<*>, vararg baseModelQueriables: BaseModelQueriable<*>): In<*> = - In(this as Operator, firstBaseModelQueriable, true, *baseModelQueriables) + @Override + public In in( BaseModelQueriable firstBaseModelQueriable, BaseModelQueriable... baseModelQueriables) { + return new In<>((Operator)this, firstBaseModelQueriable, true, baseModelQueriables); + } - override fun concatenate(value: Any?): Operator { - var _value = value - operation = "${Operation.EQUALS}${columnName()}" + @Override + public Operator concatenate(Object value) { + Object _value = value; + operation = Operation.EQUALS + columnName(); - var typeConverter: TypeConverter<*, Any>? = this.typeConverter as TypeConverter<*, Any>? + TypeConverters.TypeConverter typeConverter = (TypeConverters.TypeConverter) this.typeConverter; if (typeConverter == null && _value != null) { - typeConverter = FlowManager.getTypeConverterForClass(_value.javaClass) as TypeConverter<*, Any>? + typeConverter = (TypeConverters.TypeConverter)FlowManager.getTypeConverterForClass(_value.getClass()); } if (typeConverter != null && convertToDB) { - _value = typeConverter.getDBValue(_value) + _value = typeConverter.getDBValue(_value); } - operation = when (_value) { - is String, is IOperator<*>, is Char -> "$operation ${Operation.CONCATENATE} " - is Number -> "$operation ${Operation.PLUS} " - else -> throw IllegalArgumentException( - "Cannot concatenate the ${if (_value != null) _value.javaClass else "null"}") + + if(_value instanceof String || _value instanceof IOperator || _value instanceof Character){ + operation = operation+" "+Operation.CONCATENATE+" "; + }else if(_value instanceof Number){ + operation = operation+" "+Operation.PLUS+" "; + }else { + assert _value != null; + throw new IllegalArgumentException( + String.valueOf(_value.getClass())); } - this.value = _value - isValueSet = true - return this + + this.value = _value; + isValueSet = true; + return this; } - override fun concatenate(conditional: IConditional): Operator = - concatenate(conditional as Any) + @Override + public Operator concatenate(IConditional conditional) { + return concatenate((Object) conditional); + } /** * Turns this condition into a SQL BETWEEN operation @@ -360,81 +506,99 @@ internal constructor(nameAlias: NameAlias?, * @param value The value of the first argument of the BETWEEN clause * @return Between operator */ - override fun between(value: T): Between = Between(this, value) + @Override + public Between between(T value) { + return new Between<>(this, value); + } @SafeVarargs - override fun `in`(firstValue: T, vararg values: T): In = - In(this, firstValue, true, *values) + @Override + public final In in(T firstValue, T... values) { + return new In<>(this, firstValue, true, values); + } @SafeVarargs - override fun notIn(firstValue: T, vararg values: T): In = - In(this, firstValue, false, *values) + @Override + public final In notIn(T firstValue, T... values) { + return new In<>(this, firstValue, false, values); + } - override fun `in`(values: Collection): In = In(this, values, true) + @Override + public In in(Collection values) { + return new In<>(this, values, true); + } - override fun notIn(values: Collection): In = In(this, values, false) + @Override + public In notIn(Collection values) { + return new In<>(this, values, false); + } - override fun convertObjectToString(obj: Any?, appendInnerParenthesis: Boolean): String = - (typeConverter as? TypeConverter<*, Any>?)?.let { typeConverter -> - var converted = obj + @Override + public String convertObjectToString(Object obj, boolean appendInnerParenthesis) { + TypeConverters.TypeConverter newTypeConverter = (TypeConverters.TypeConverter)typeConverter; + if(newTypeConverter != null){ + Object converted = obj; try { - converted = if (convertToDB) typeConverter.getDBValue(obj) else obj - } catch (c: ClassCastException) { + converted = convertToDB? newTypeConverter.getDBValue(obj) : obj; + } catch (ClassCastException c) { // if object type is not valid converted type, just use type as is here. // if object type is not valid converted type, just use type as is here. FlowLog.log(FlowLog.Level.I, "Value passed to operation is not valid type" + - " for TypeConverter in the column. Preserving value $obj to be used as is.") + " for TypeConverter in the column. Preserving value $obj to be used as is."); } - convertValueToString(converted, appendInnerParenthesis, false) - } ?: super.convertObjectToString(obj, appendInnerParenthesis) + return convertValueToString(converted, appendInnerParenthesis, false); + }else { + return super.convertObjectToString(obj, appendInnerParenthesis); + } + } - private fun assignValueOp(value: Any?, operation: String): Operator { - return if (!isValueSet) { - this.operation = operation - value(value) + private Operator assignValueOp(Object value, String operation) { + if (!isValueSet) { + this.operation = operation; + return value(value); } else { - convertToString = false + convertToString = false; // convert value to a string value because of conversion. - value(convertValueToString(this.value) + operation + convertValueToString(value)) + return value(convertValueToString(this.value) + operation + convertValueToString(value)); } } /** * Static constants that define condition operations */ - object Operation { + public static class Operation { /** * Equals comparison */ - const val EQUALS = "=" + public static final String EQUALS = "="; /** * Not-equals comparison */ - const val NOT_EQUALS = "!=" + public static final String NOT_EQUALS = "!="; /** * String concatenation */ - const val CONCATENATE = "||" + public static final String CONCATENATE = "||"; /** * Number addition */ - const val PLUS = "+" + public static final String PLUS = "+"; /** * Number subtraction */ - const val MINUS = "-" + public static final String MINUS = "-"; - const val DIVISION = "/" + public static final String DIVISION = "/"; - const val MULTIPLY = "*" + public static final String MULTIPLY = "*"; - const val MOD = "%" + public static final String MOD = "%"; /** * If something is LIKE another (a case insensitive search). @@ -442,14 +606,14 @@ internal constructor(nameAlias: NameAlias?, * % represents [0,many) numbers or characters. * The _ represents a single number or character. */ - const val LIKE = "LIKE" + public static final String LIKE = "LIKE"; /** * If the WHERE clause of the SELECT statement contains a sub-clause of the form " MATCH ?", * FTS is able to use the built-in full-text index to restrict the search to those documents * that match the full-text query string specified as the right-hand operand of the MATCH clause. */ - const val MATCH = "MATCH" + public static final String MATCH = "MATCH"; /** * If something is NOT LIKE another (a case insensitive search). @@ -457,7 +621,7 @@ internal constructor(nameAlias: NameAlias?, * % represents [0,many) numbers or characters. * The _ represents a single number or character. */ - const val NOT_LIKE = "NOT LIKE" + public static final String NOT_LIKE = "NOT LIKE"; /** * If something is case sensitive like another. @@ -466,115 +630,112 @@ internal constructor(nameAlias: NameAlias?, * * represents [0,many) numbers or characters. * The ? represents a single number or character */ - const val GLOB = "GLOB" + public static final String GLOB = "GLOB"; /** * Greater than some value comparison */ - const val GREATER_THAN = ">" + public static final String GREATER_THAN = ">"; /** * Greater than or equals to some value comparison */ - const val GREATER_THAN_OR_EQUALS = ">=" + public static final String GREATER_THAN_OR_EQUALS = ">="; /** * Less than some value comparison */ - const val LESS_THAN = "<" + public static final String LESS_THAN = "<"; /** * Less than or equals to some value comparison */ - const val LESS_THAN_OR_EQUALS = "<=" + public static final String LESS_THAN_OR_EQUALS = "<="; /** * Between comparison. A simplification of X<Y AND Y<Z to Y BETWEEN X AND Z */ - const val BETWEEN = "BETWEEN" + public static final String BETWEEN = "BETWEEN"; /** * AND comparison separator */ - const val AND = "AND" + public static final String AND = "AND"; /** * OR comparison separator */ - const val OR = "OR" + public static final String OR = "OR"; /** * An empty value for the condition. */ - @Deprecated(replaceWith = ReplaceWith( - expression = "Property.WILDCARD", - imports = ["com.dbflow5.query.Property"] - ), message = "Deprecated. This will translate to '?' in the query as it get's SQL-escaped. " + - "Use the Property.WILDCARD instead to get desired ? behavior.") - const val EMPTY_PARAM = "?" + @Deprecated + public static final String EMPTY_PARAM = "?"; /** * Special operation that specify if the column is not null for a specified row. Use of this as * an operator will ignore the value of the [Operator] for it. */ - const val IS_NOT_NULL = "IS NOT NULL" + public static final String IS_NOT_NULL = "IS NOT NULL"; /** * Special operation that specify if the column is null for a specified row. Use of this as * an operator will ignore the value of the [Operator] for it. */ - const val IS_NULL = "IS NULL" + public static final String IS_NULL = "IS NULL"; /** * The SQLite IN command that will select rows that are contained in a list of values. * EX: SELECT * from Table where column IN ('first', 'second', etc) */ - const val IN = "IN" + public static final String IN = "IN"; /** * The reverse of the [.IN] command that selects rows that are not contained * in a list of values specified. */ - const val NOT_IN = "NOT IN" + public static final String NOT_IN = "NOT IN"; } /** * The SQL BETWEEN operator that contains two values instead of the normal 1. */ - class Between - /** - * Creates a new instance - * - * @param operator - * @param value The value of the first argument of the BETWEEN clause - */ - internal constructor(operator: Operator, value: T) : BaseOperator(operator.nameAlias), Query { + public static class Between extends BaseOperator implements Query { - private var secondValue: T? = null + private V secondValue = null; - override val query: String - get() = appendToQuery() + Between(Operator operator, V value) { + super(operator.nameAlias); + this.operation = " "+Operation.BETWEEN+" "; + this.value = value; + isValueSet = true; + this.postArg = operator.postArgument(); + } - init { - this.operation = " ${Operation.BETWEEN} " - this.value = value - isValueSet = true - this.postArg = operator.postArgument() + @Override + public String getQuery() { + return appendToQuery(); } - infix fun and(secondValue: T?) = apply { - this.secondValue = secondValue + public Between and(V secondValue) { + this.secondValue = secondValue; + return this; } - fun secondValue(): T? = secondValue + public V secondValue() { + return secondValue; + } - override fun appendConditionToQuery(queryBuilder: StringBuilder) { + @Override + public void appendConditionToQuery(StringBuilder queryBuilder) { queryBuilder.append(columnName()).append(operation()) .append(convertObjectToString(value(), true)) - .append(" ${Operation.AND} ") + .append(" "+Operation.AND+" ") .append(convertObjectToString(secondValue(), true)) - .append(" ") - .appendOptional(postArgument()) + .append(" "); + + StringUtils.appendOptional(queryBuilder, postArgument()); } } @@ -582,12 +743,14 @@ internal constructor(nameAlias: NameAlias?, * The SQL IN and NOT IN operator that specifies a list of values to SELECT rows from. * EX: SELECT * FROM myTable WHERE columnName IN ('column1','column2','etc') */ - class In : BaseOperator, Query { + public static class In extends BaseOperator implements Query { - private val inArguments = arrayListOf() + private final List inArguments = new ArrayList<>(); - override val query: String - get() = appendToQuery() + @Override + public String getQuery() { + return appendToQuery(); + } /** * Creates a new instance @@ -598,15 +761,25 @@ internal constructor(nameAlias: NameAlias?, * statement or a [Operator.Operation.NOT_IN] */ @SafeVarargs - internal constructor(operator: Operator, firstArgument: T?, isIn: Boolean, vararg arguments: T?) : super(operator.columnAlias()) { - inArguments.add(firstArgument) - inArguments.addAll(arguments) - operation = " ${if (isIn) Operation.IN else Operation.NOT_IN} " + In(Operator operator, V firstArgument, boolean isIn, V... arguments) { + super(operator.columnAlias()); + inArguments.add(firstArgument); + inArguments.addAll(Arrays.asList(arguments)); + if(isIn){ + operation = Operation.IN; + }else { + operation = Operation.NOT_IN; + } } - internal constructor(operator: Operator, args: Collection, isIn: Boolean) : super(operator.columnAlias()) { - inArguments.addAll(args) - operation = " ${if (isIn) Operation.IN else Operation.NOT_IN} " + In(Operator operator, Collection args, boolean isIn) { + super(operator.columnAlias()); + inArguments.addAll(args); + if(isIn){ + operation = Operation.IN; + }else { + operation = Operation.NOT_IN; + } } /** @@ -616,56 +789,67 @@ internal constructor(nameAlias: NameAlias?, * in a [OperatorGroup]. * @return */ - infix fun and(argument: T?): In { - inArguments.add(argument) - return this + public In and(V argument) { + inArguments.add(argument); + return this; } - override fun appendConditionToQuery(queryBuilder: StringBuilder) { + @Override + public void appendConditionToQuery(StringBuilder queryBuilder) { queryBuilder.append(columnName()) .append(operation()) .append("(") .append(joinArguments(",", inArguments, this)) - .append(")") + .append(")"); } } - companion object { - - @JvmStatic - fun convertValueToString(value: Any?): String? = - convertValueToString(value, false) + public static String convertValueToString(Object value) { + return convertValueToString(value, false); + } - @JvmStatic - fun op(column: NameAlias): Operator = Operator(column, convertToDB = false) + public static Operator op(NameAlias column) { + return new Operator<>(column, null, null, false); + } - @JvmStatic - fun op(alias: NameAlias, table: Class<*>, - getter: TypeConvertedProperty.TypeConverterGetter, - convertToDB: Boolean): Operator = - Operator(alias, table, getter, convertToDB) + public static Operator op(NameAlias alias, Class table, TypeConvertedProperty.TypeConverterGetter getter, boolean convertToDB) { + return new Operator<>(alias, table, getter, convertToDB); } -} + public static Operator op(String column) { + return NameAlias.nameAlias(column).op(); + } -fun NameAlias.op() = Operator.op(this) + public OperatorGroup and(SQLOperator sqlOperator) { + return OperatorGroup.clause(this).and(sqlOperator); + } -fun String.op(): Operator = nameAlias.op() + public OperatorGroup or(SQLOperator sqlOperator) { + return OperatorGroup.clause(this).or(sqlOperator); + } -infix fun Operator.and(sqlOperator: SQLOperator): OperatorGroup = OperatorGroup.clause(this).and(sqlOperator) + public OperatorGroup andAll(Collection sqlOperator) { + return OperatorGroup.clause(this).andAll(sqlOperator); + } -infix fun Operator.or(sqlOperator: SQLOperator): OperatorGroup = OperatorGroup.clause(this).or(sqlOperator) + public OperatorGroup orAll(Collection sqlOperator) { + return OperatorGroup.clause(this).orAll(sqlOperator); + } -infix fun Operator.andAll(sqlOperator: Collection): OperatorGroup = OperatorGroup.clause(this).andAll(sqlOperator) + public Operator.In in(T[] values) { + if(values.length == 1){ + return in(values[0]); + }else { + return this.in(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } -infix fun Operator.orAll(sqlOperator: Collection): OperatorGroup = OperatorGroup.clause(this).orAll(sqlOperator) + public Operator.In notIn(T[] values) { + if(values.length == 1){ + return notIn(values[0]); + }else { + return this.notIn(values[0], Arrays.copyOfRange(values, 1, values.length)); + } + } -infix fun Operator.`in`(values: Array): Operator.In = when (values.size) { - 1 -> `in`(values[0]) - else -> this.`in`(values[0], *values.sliceArray(IntRange(1, values.size))) } - -infix fun Operator.notIn(values: Array): Operator.In = when (values.size) { - 1 -> notIn(values[0]) - else -> this.notIn(values[0], *values.sliceArray(IntRange(1, values.size))) -} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/query/OperatorGroup.java b/lib/src/main/java/com/dbflow5/query/OperatorGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..f3a2439d3e38acb867c783b4e4f9516721ca43eb --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/OperatorGroup.java @@ -0,0 +1,233 @@ +package com.dbflow5.query; + +import com.dbflow5.query.Operator.Operation; +import com.dbflow5.sql.Query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Allows combining of [SQLOperator] into one condition. + */ +public class OperatorGroup extends BaseOperator implements Query, Iterable { + + private final List conditionsList = new ArrayList<>(); + + private String internalQuery = null; + private boolean isChanged; + private boolean allCommaSeparated; + private boolean useParenthesis = true; + + public List conditions() { + return conditionsList; + } + + private String querySafe() { + return appendToQuery(); + } + + public OperatorGroup(NameAlias columnName) { + super(columnName); + // default is AND + separator = Operation.AND; + } + + /** + * Will ignore all separators for the group and make them separated by comma. This is useful + * in [Set] statements. + * + * @param allCommaSeparated All become comma separated. + * @return This instance. + */ + public OperatorGroup setAllCommaSeparated(boolean allCommaSeparated) { + this.allCommaSeparated = allCommaSeparated; + isChanged = true; + return this; + } + + /** + * Sets whether we use paranthesis when grouping this within other [SQLOperator]. The default + * is true, but if no conditions exist there are no paranthesis anyways. + * + * @param useParenthesis true if we use them, false if not. + */ + public OperatorGroup setUseParenthesis(boolean useParenthesis) { + this.useParenthesis = useParenthesis; + isChanged = true; + return this; + } + + /** + * Appends the [SQLOperator] with an [Operation.OR] + * + * @param sqlOperator The condition to append. + * @return This instance. + */ + public OperatorGroup or(SQLOperator sqlOperator) { + return operator(Operation.OR, sqlOperator); + } + + /** + * Appends the [SQLOperator] with an [Operation.AND] + */ + public OperatorGroup and(SQLOperator sqlOperator) { + return operator(Operation.AND, sqlOperator); + } + + /** + * Applies the [Operation.AND] to all of the passed + * [SQLOperator]. + */ + public OperatorGroup andAll(SQLOperator... sqlOperators) { + for(SQLOperator sqlOperator : sqlOperators) { + and(sqlOperator); + } + return this; + } + + /** + * Applies the [Operation.AND] to all of the passed + * [SQLOperator]. + */ + public OperatorGroup andAll(Collection sqlOperators) { + for(SQLOperator sqlOperator : sqlOperators) { + and(sqlOperator); + } + return this; + } + + /** + * Applies the [Operation.AND] to all of the passed + * [SQLOperator]. + */ + public OperatorGroup orAll(SQLOperator... sqlOperators) { + for(SQLOperator sqlOperator : sqlOperators) { + or(sqlOperator); + } + return this; + } + + /** + * Applies the [Operation.AND] to all of the passed + * [SQLOperator]. + */ + public OperatorGroup orAll(Collection sqlOperators) { + for(SQLOperator sqlOperator : sqlOperators) { + or(sqlOperator); + } + return this; + } + + /** + * Appends the [SQLOperator] with the specified operator string. + */ + public OperatorGroup operator(String operator, SQLOperator sqlOperator) { + if (sqlOperator != null) { + setPreviousSeparator(operator); + conditionsList.add(sqlOperator); + isChanged = true; + } + return this; + } + + public void appendConditionToQuery(StringBuilder queryBuilder) { + int conditionListSize = conditionsList.size(); + if (useParenthesis && conditionListSize > 0) { + queryBuilder.append("("); + } + for (int i = 0; i < conditionListSize; i++) { + SQLOperator condition = conditionsList.get(i); + condition.appendConditionToQuery(queryBuilder); + if (!allCommaSeparated && condition.hasSeparator() && i < conditionListSize - 1) { + queryBuilder.append(" ").append(condition.separator()).append(" "); + } else if (i < conditionListSize - 1) { + queryBuilder.append(", "); + } + } + if (useParenthesis && conditionListSize > 0) { + queryBuilder.append(")"); + } + } + + /** + * Sets the last condition to use the separator specified + * + * @param separator AND, OR, etc. + */ + private void setPreviousSeparator(String separator) { + if (conditionsList.size() > 0) { + // set previous to use OR separator + conditionsList.get(conditionsList.size() - 1).separator(separator); + } + } + + @Override + public String getQuery() { + if (isChanged) { + internalQuery = querySafe(); + } + return internalQuery == null ? "" : internalQuery; + } + + @Override + public String toString() { + return querySafe(); + } + + public int size() { + return conditionsList.size(); + } + + @Override + public Iterator iterator() { + return conditionsList.iterator(); + } + + /** + * @return Starts an arbitrary clause of conditions to use. + */ + public static OperatorGroup clause() { + return new OperatorGroup(null); + } + + /** + * @return Starts an arbitrary clause of conditions to use with first param as conditions separated by AND. + */ + public static OperatorGroup clause(SQLOperator... condition) { + return new OperatorGroup(null).andAll(condition); + } + + /** + * @return Starts an arbitrary clause of conditions to use, that when included in other [SQLOperator], + * does not append parenthesis to group it. + */ + public static OperatorGroup nonGroupingClause() { + return new OperatorGroup(null).setUseParenthesis(false); + } + + /** + * @return Starts an arbitrary clause of conditions (without parenthesis) to use with first param as conditions separated by AND. + */ + public static OperatorGroup nonGroupingClause(SQLOperator... condition) { + return new OperatorGroup(null).setUseParenthesis(false).andAll(condition); + } + +// public OperatorGroup and(SQLOperator sqlOperator) { +// return and(sqlOperator); +// } +// +// public OperatorGroup or(SQLOperator sqlOperator) { +// return or(sqlOperator); +// } +// +// public OperatorGroup and(OperatorGroup sqlOperator) { +// return clause().and(sqlOperator); +// } +// +// public OperatorGroup or(OperatorGroup sqlOperator) { +// return clause().or(sqlOperator); +// } +} + diff --git a/lib/src/main/java/com/dbflow5/query/OrderBy.java b/lib/src/main/java/com/dbflow5/query/OrderBy.java new file mode 100644 index 0000000000000000000000000000000000000000..c0497b2195980b778c55f82e3216427b76c65997 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/OrderBy.java @@ -0,0 +1,96 @@ +package com.dbflow5.query; + +import com.dbflow5.annotation.Collate; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.sql.Query; + +/** + * Description: Class that represents a SQL order-by. + */ +public class OrderBy implements Query { + public static final String ASCENDING = "ASC"; + public static final String DESCENDING = "DESC"; + + private Collate collation = null; + private String orderByString; + + private NameAlias column = null; + /** + * If true, append ASC, if false append DESC, or if null, then don't append. + */ + private boolean isAscending = true; + + public OrderBy(NameAlias column, boolean isAscending){ + this.column = column; + this.isAscending = isAscending; + + this.orderByString = null; + } + + @Override + public String getQuery() { + String locOrderByString = orderByString; + if (locOrderByString == null) { + StringBuilder query = new StringBuilder() + .append(column) + .append(" "); + if (collation != null) { + query.append("COLLATE ").append(collation).append(" "); + } + query.append(isAscending? ASCENDING : DESCENDING); + return query.toString(); + } else { + return locOrderByString; + } + } + + public OrderBy(IProperty property) { + this(property.nameAlias(), true); + } + + public OrderBy(String orderByString) { + this(null, true); + this.orderByString = orderByString; + } + + public OrderBy ascending() { + isAscending = true; + return this; + } + + public OrderBy descending() { + isAscending = false; + return this; + } + + public OrderBy collate(Collate collate) { + this.collation = collate; + return this; + } + + @Override + public String toString() { + return getQuery(); + } + + public static OrderBy fromProperty(IProperty property, boolean isAscending) { + return new OrderBy(property.nameAlias(), + // if we use RANDOM(), leave out ascending qualifier as its not valid SQLite. + property.equals(Method.random()) || isAscending); + } + + public static OrderBy fromNameAlias(NameAlias nameAlias, boolean isAscending) { + return new OrderBy(nameAlias, isAscending); + } + + /** + * Starts an [OrderBy] with RANDOM() query. + */ + public OrderBy random() { + return new OrderBy(Method.random().nameAlias(), true); + } + + public static OrderBy fromString(String orderByString) { + return new OrderBy(orderByString); + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/Queriable.kt b/lib/src/main/java/com/dbflow5/query/Queriable.java similarity index 45% rename from lib/src/main/kotlin/com/dbflow5/query/Queriable.kt rename to lib/src/main/java/com/dbflow5/query/Queriable.java index 56fd0f869389462ebb3cbb4d1e5a9d4b1abc8a44..3ac4cb0216d7809be49bc143c624e96c332bd78e 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/Queriable.kt +++ b/lib/src/main/java/com/dbflow5/query/Queriable.java @@ -1,61 +1,82 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.sql.Query -import com.dbflow5.structure.ChangeAction -import com.dbflow5.structure.Model +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.database.FlowCursor; +import com.dbflow5.sql.Query; +import com.dbflow5.structure.ChangeAction; +import com.dbflow5.structure.Model; /** * Description: The most basic interface that some of the classes such as [Insert], [ModelQueriable], * [Set], and more implement for convenience. */ -interface Queriable : Query { +public interface Queriable extends Query { - val primaryAction: ChangeAction + /** + * + * @return ChangeAction + */ + ChangeAction primaryAction(); /** + * cursor + * + * @param databaseWrapper databaseWrapper * @return A cursor from the DB based on this query */ - fun cursor(databaseWrapper: DatabaseWrapper): FlowCursor? + FlowCursor cursor(DatabaseWrapper databaseWrapper); /** + * compileStatement + * + * @param databaseWrapper databaseWrapper * @return A new [DatabaseStatement] from this query. */ - fun compileStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement + DatabaseStatement compileStatement(DatabaseWrapper databaseWrapper); /** + * longValue + * + * @param databaseWrapper databaseWrapper * @return the long value of the results of a query or single-column result. */ - fun longValue(databaseWrapper: DatabaseWrapper): Long + long longValue(DatabaseWrapper databaseWrapper); /** + * stringValue + * + * @param databaseWrapper databaseWrapper * @return the string value for results of a query or single-column result. */ - fun stringValue(databaseWrapper: DatabaseWrapper): String? + String stringValue(DatabaseWrapper databaseWrapper); /** + * @param databaseWrapper databaseWrapper * @return This may return the number of rows affected from a [Set] or [Delete] statement. * If not, returns [Model.INVALID_ROW_ID] */ - fun executeUpdateDelete(databaseWrapper: DatabaseWrapper): Long + long executeUpdateDelete(DatabaseWrapper databaseWrapper); /** + * @param databaseWrapper databaseWrapper * @return This may return the number of rows affected from a [Insert] statement. * If not, returns [Model.INVALID_ROW_ID] */ - fun executeInsert(databaseWrapper: DatabaseWrapper): Long + long executeInsert(DatabaseWrapper databaseWrapper); /** + * hasData + * + * @param databaseWrapper databaseWrapper * @return True if this query has data. It will run a [.count] greater than 0. */ - fun hasData(databaseWrapper: DatabaseWrapper): Boolean + boolean hasData(DatabaseWrapper databaseWrapper); /** * Will not return a result, rather simply will execute a SQL statement. Use this for non-SELECT statements or when * you're not interested in the result. */ - fun execute(databaseWrapper: DatabaseWrapper) + void execute(DatabaseWrapper databaseWrapper); } \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/SQLOperator.kt b/lib/src/main/java/com/dbflow5/query/SQLOperator.java similarity index 58% rename from lib/src/main/kotlin/com/dbflow5/query/SQLOperator.kt rename to lib/src/main/java/com/dbflow5/query/SQLOperator.java index 4a551a631b3fd032250425844d34e02cf985cd43..e34f231ecf9d4184cebbb3a20c5fb29555fe8a41 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/SQLOperator.kt +++ b/lib/src/main/java/com/dbflow5/query/SQLOperator.java @@ -1,30 +1,30 @@ -package com.dbflow5.query +package com.dbflow5.query; /** * Description: Basic interface for all of the Operator classes. */ -interface SQLOperator { +public interface SQLOperator { /** * Appends itself to the [StringBuilder] * * @param queryBuilder The builder to append to. */ - fun appendConditionToQuery(queryBuilder: StringBuilder) + void appendConditionToQuery(StringBuilder queryBuilder); /** * The name of the column. * * @return The column name. */ - fun columnName(): String + String columnName(); /** * The separator for this condition when paired with a [OperatorGroup] * * @return The separator, an AND, OR, or other kinds. */ - fun separator(): String? + String separator(); /** * Sets the separator for this condition @@ -32,27 +32,32 @@ interface SQLOperator { * @param separator The string AND, OR, or something else. * @return This instance. */ - fun separator(separator: String): SQLOperator + SQLOperator separator(String separator); /** * @return true if it has a separator, false if not. */ - fun hasSeparator(): Boolean + boolean hasSeparator(); /** * @return the operation that is used. */ - fun operation(): String + String operation(); /** * @return The raw value of the condition. */ - fun value(): Any? + Object value(); + + default String appendToQuery() { + StringBuilder queryBuilder = new StringBuilder(); + appendConditionToQuery(queryBuilder); + return queryBuilder.toString(); + } + + default OperatorGroup clause2() { + return OperatorGroup.clause(this); + } } -fun SQLOperator.appendToQuery(): String { - val queryBuilder = StringBuilder() - appendConditionToQuery(queryBuilder) - return queryBuilder.toString() -} \ No newline at end of file diff --git a/lib/src/main/java/com/dbflow5/query/SQLite.java b/lib/src/main/java/com/dbflow5/query/SQLite.java new file mode 100644 index 0000000000000000000000000000000000000000..6b8dc1f16804768c4cc6c4cfbd29c193ff98640a --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/SQLite.java @@ -0,0 +1,137 @@ +package com.dbflow5.query; + +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import ohos.utils.Pair; + + +public class SQLite{ + /** + * @param properties The properties/columns to SELECT. + * @return A beginning of the SELECT statement. + */ + public static Select select(IProperty... properties) { + return new Select(properties); + } + + /** + * Starts a new SELECT COUNT(property1, property2, propertyn) (if properties specified) or + * SELECT COUNT(*). + * + * @param properties Optional, if specified returns the count of non-null ROWs from a specific single/group of columns. + * @return A new select statement SELECT COUNT(expression) + */ + public static Select selectCountOf(IProperty... properties) { + return new Select(Method.count(properties)); + } + + /** + * @param table The tablet to update. + * @return A new UPDATE statement. + */ + public static Update update(Class table) { + return new Update<>(table); + } + + public static Insert insertInto(Class clazz) { + return insert(clazz, new Property[]{}); + } + + @SafeVarargs + public static Insert insert(Class table, Pair, ?>... columnValues) { + return new Insert<>(table).columnValues(columnValues); + } + + public static Insert insert(Class table, SQLOperator... operators) { + return new Insert<>(table).columnValues(operators); + } + + public static Insert insert(Class table) { + return new Insert<>(table); + } + + /** + * @param table The table to insert. + * @return A new INSERT statement. + */ + public static Insert insert(Class table, Property... columns) { + return new Insert<>(table, columns); + } + + /** + * @return Begins a DELETE statement. + */ + public static Delete delete() { + return new Delete(); + } + + /** + * Starts a DELETE statement on the specified table. + * + * @param table The table to delete from. + * @return A [From] with specified DELETE on table. + */ + public static From delete(Class table) { + return delete().from(table); + } + + /** + * Starts an INDEX statement on specified table. + * + * @param name The name of the index. + * @return A new INDEX statement. + */ + public static Index index(String name, Class table) { + return new Index<>(name, table); + } + + /** + * Starts a TRIGGER statement. + * + * @param name The name of the trigger. + * @return A new TRIGGER statement. + */ + public static Trigger createTrigger(String name) { + return Trigger.create(name); + } + + /** + * Starts a temporary TRIGGER statement. + * + * @param name The name of the trigger. + * @return A new TEMPORARY TRIGGER statement. + */ + public static Trigger createTempTrigger(String name) { + return Trigger.create(name).temporary(); + } + + /** + * Starts a CASE statement. + * + * @param operator The condition to check for in the WHEN. + * @return A new [CaseCondition]. + */ + public static CaseCondition caseWhen(SQLOperator operator) { + return new Case().whenever(operator); + } + + /** + * Starts an efficient CASE statement. The value passed here is only evaulated once. A non-efficient + * case statement will evaluate all of its [SQLOperator]. + * + * @param caseColumn The value + */ + public static Case _case(Property caseColumn) { + return new Case<>(caseColumn); + } + + /** + * Starts an efficient CASE statement. The value passed here is only evaulated once. A non-efficient + * case statement will evaluate all of its [SQLOperator]. + * + * @param caseColumn The value + */ + public static Case _case(IProperty caseColumn) { + return new Case<>(caseColumn); + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/SQLiteStatementListener.kt b/lib/src/main/java/com/dbflow5/query/SQLiteStatementListener.java similarity index 58% rename from lib/src/main/kotlin/com/dbflow5/query/SQLiteStatementListener.kt rename to lib/src/main/java/com/dbflow5/query/SQLiteStatementListener.java index 6a74d9742115d5218f91c7a1de49e74fa7dc6363..cc602fd88d42e79b61c7682b8a6ad253897fc3f8 100644 --- a/lib/src/main/kotlin/com/dbflow5/query/SQLiteStatementListener.kt +++ b/lib/src/main/java/com/dbflow5/query/SQLiteStatementListener.java @@ -1,15 +1,12 @@ -package com.dbflow5.query +package com.dbflow5.query; -import com.dbflow5.adapter.InternalAdapter -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.structure.Model +import com.dbflow5.database.DatabaseStatement; /** * Description: Marks a [Model] as subscribing to the [DatabaseStatement] * that is used to [Model.insert] a model into the DB. */ -interface SQLiteStatementListener { +public interface SQLiteStatementListener { /** * Called at the end of [InternalAdapter.bindToInsertStatement] @@ -17,7 +14,7 @@ interface SQLiteStatementListener { * * @param databaseStatement The insert statement from the [ModelAdapter] */ - fun onBindToInsertStatement(databaseStatement: DatabaseStatement) + void onBindToInsertStatement(DatabaseStatement databaseStatement); /** * Called at the end of [InternalAdapter.bindToUpdateStatement] @@ -25,7 +22,7 @@ interface SQLiteStatementListener { * * @param databaseStatement The insert statement from the [ModelAdapter] */ - fun onBindToUpdateStatement(databaseStatement: DatabaseStatement) + void onBindToUpdateStatement(DatabaseStatement databaseStatement); - fun onBindToDeleteStatement(databaseStatement: DatabaseStatement) + void onBindToDeleteStatement(DatabaseStatement databaseStatement); } diff --git a/lib/src/main/java/com/dbflow5/query/Select.java b/lib/src/main/java/com/dbflow5/query/Select.java new file mode 100644 index 0000000000000000000000000000000000000000..26defdeac8619330c7cde3ed70fbe381577f6343 --- /dev/null +++ b/lib/src/main/java/com/dbflow5/query/Select.java @@ -0,0 +1,122 @@ +package com.dbflow5.query; + +import com.dbflow5.StringUtils; +import com.dbflow5.query.property.IProperty; +import com.dbflow5.query.property.Property; +import com.dbflow5.sql.Query; +import com.dbflow5.sql.QueryCloneable; +import ohos.agp.utils.TextTool; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Description: A SQL SELECT statement generator. It generates the SELECT part of the statement. + */ +public class Select implements Query, QueryCloneable { - - /** - * The select qualifier to append to the SELECT statement - */ - private var selectQualifier: QualifierType = None - - private val propertyList = arrayListOf>() - - override val query: String - get() { - val queryBuilder = StringBuilder("SELECT ") - when (selectQualifier) { - Distinct -> queryBuilder.append("DISTINCT ") - All -> queryBuilder.append("ALL ") - } - - queryBuilder.append(propertyList.joinToString(separator = ",")) - queryBuilder.append(" ") - return queryBuilder.toString() - } - - init { - propertyList.addAll(properties.toList()) - if (propertyList.isEmpty()) { - propertyList.add(Property.ALL_PROPERTY) - } - } - - /** - * Passes this statement to the [From] - * - * @param table The model table to run this query on - * @param [T] The class that implements [com.dbflow5.structure.Model] - * @return the From part of this query - */ - fun from(table: Class): From = From(this, table) - - inline fun from() = from(T::class.java) - - infix fun from(table: KClass) = from(table.java) - - /** - * Constructs a [From] with a [ModelQueriable] expression. - */ - fun from(modelQueriable: ModelQueriable) = From(this, modelQueriable.table, modelQueriable) - - /** - * appends [.DISTINCT] to the query - * - * @return - */ - fun distinct(): Select = selectQualifier(Distinct) - - override fun toString(): String = query - - override fun cloneSelf(): Select = Select(*propertyList.toTypedArray()) - - /** - * Helper method to pick the correct qualifier for a SELECT query - * - * @param qualifierInt Can be [.ALL], [.NONE], or [.DISTINCT] - * @return - */ - private fun selectQualifier(qualifier: QualifierType) = apply { - selectQualifier = qualifier - } -} - -inline val select: Select - get() = select() diff --git a/lib/src/main/kotlin/com/dbflow5/query/Set.kt b/lib/src/main/kotlin/com/dbflow5/query/Set.kt deleted file mode 100644 index 83fe63a0a270c723b72929b68c526d8ac2755740..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/Set.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.dbflow5.query - -import android.content.ContentValues -import com.dbflow5.addContentValues -import com.dbflow5.sql.Query -import com.dbflow5.structure.ChangeAction - -/** - * Description: Used to specify the SET part of an [com.dbflow5.query.Update] query. - */ -class Set internal constructor( - override val queryBuilderBase: Query, table: Class) - : BaseTransformable(table), WhereBase { - - private val operatorGroup: OperatorGroup = OperatorGroup.nonGroupingClause().setAllCommaSeparated(true) - - override val query: String - get() = " ${queryBuilderBase.query}SET ${operatorGroup.query} " - - override val primaryAction: ChangeAction - get() = ChangeAction.UPDATE - - /** - * Specifies a varg of conditions to append to this SET - * - * @param conditions The varg of conditions - * @return This instance. - */ - fun conditions(vararg conditions: SQLOperator) = apply { - operatorGroup.andAll(*conditions) - } - - /** - * Specifies a varg of conditions to append to this SET - * - * @param condition The varg of conditions - * @return This instance. - */ - infix fun and(condition: SQLOperator) = apply { - operatorGroup.and(condition) - } - - fun conditionValues(contentValues: ContentValues) = apply { - addContentValues(contentValues, operatorGroup) - } - - override fun cloneSelf(): Set { - val set = Set( - when (queryBuilderBase) { - is Update<*> -> queryBuilderBase.cloneSelf() - else -> queryBuilderBase - }, table) - set.operatorGroup.andAll(operatorGroup.conditions) - return set - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/StringQuery.kt b/lib/src/main/kotlin/com/dbflow5/query/StringQuery.kt deleted file mode 100644 index b00f0056d7452ec5930cf11f778c9325adb612ff..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/StringQuery.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.dbflow5.query - -import android.database.sqlite.SQLiteDatabase -import com.dbflow5.config.FlowLog -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.DatabaseStatementWrapper -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.sql.Query -import com.dbflow5.structure.ChangeAction - -/** - * Description: Provides a very basic query mechanism for strings. Allows you to easily perform custom SQL cursor string - * code where this library does not provide. It only runs a - * [SQLiteDatabase.rawQuery]. - */ -class StringQuery -/** - * Creates an instance of this class - * - * @param table The table to use - * @param query The sql statement to query the DB with. Does not work with [Delete], - * this must be done with [DatabaseWrapper.execSQL] - */ -(table: Class, - override val query: String) - : BaseModelQueriable(table), Query, ModelQueriable { - private var args: Array? = null - - override// we don't explicitly know the change, but something changed. - val primaryAction: ChangeAction - get() = ChangeAction.CHANGE - - override fun cursor(databaseWrapper: DatabaseWrapper): FlowCursor? = databaseWrapper.rawQuery(query, args) - - override fun queryList(databaseWrapper: DatabaseWrapper): MutableList { - FlowLog.log(FlowLog.Level.V, "Executing query: " + query) - return listModelLoader.load(cursor(databaseWrapper), databaseWrapper)!! - } - - override fun querySingle(databaseWrapper: DatabaseWrapper): T? { - FlowLog.log(FlowLog.Level.V, "Executing query: " + query) - return singleModelLoader.load(cursor(databaseWrapper), databaseWrapper)!! - } - - override fun queryCustomList(queryModelClass: Class, - databaseWrapper: DatabaseWrapper) - : MutableList { - val query = query - FlowLog.log(FlowLog.Level.V, "Executing query: " + query) - return getListQueryModelLoader(queryModelClass) - .load(cursor(databaseWrapper), databaseWrapper)!! - } - - override fun queryCustomSingle(queryModelClass: Class, - databaseWrapper: DatabaseWrapper) - : QueryClass? { - val query = query - FlowLog.log(FlowLog.Level.V, "Executing query: " + query) - return getSingleQueryModelLoader(queryModelClass) - .load(cursor(databaseWrapper), databaseWrapper) - } - - override fun compileStatement(databaseWrapper: DatabaseWrapper): DatabaseStatement { - val query = query - FlowLog.log(FlowLog.Level.V, "Compiling Query Into Statement: query = $query , args = $args") - return DatabaseStatementWrapper(databaseWrapper.compileStatement(query, args), this) - } - - /** - * Set selection arguments to execute on this raw query. - */ - fun setSelectionArgs(args: Array) = apply { - this.args = args - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/Transformable.kt b/lib/src/main/kotlin/com/dbflow5/query/Transformable.kt deleted file mode 100644 index 12e690cd65623270d96187fd66e017a4c69b338b..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/Transformable.kt +++ /dev/null @@ -1,50 +0,0 @@ -@file:JvmName("TransformableUtils") - -package com.dbflow5.query - -import com.dbflow5.query.property.IProperty -import com.dbflow5.sql.QueryCloneable - -/** - * Description: Provides a standard set of methods for ending a SQLite query method. These include - * groupby, orderby, having, limit and offset. - */ -interface Transformable { - - fun groupBy(vararg nameAliases: NameAlias): Where - - fun groupBy(vararg properties: IProperty<*>): Where - - fun orderBy(nameAlias: NameAlias, ascending: Boolean = true): Where - - fun orderBy(property: IProperty<*>, ascending: Boolean = true): Where - - infix fun orderBy(orderBy: OrderBy): Where - - infix fun limit(count: Long): Where - - infix fun offset(offset: Long): Where - - fun having(vararg conditions: SQLOperator): Where - - fun orderByAll(orderByList: List): Where - - /** - * Constrains the given [Transformable] by the [offset] and [limit] specified. It copies over itself - * into a new instance to not preserve changes. - */ - fun constrain(offset: Long, limit: Long): ModelQueriable { - var tr: Transformable = this - @Suppress("UNCHECKED_CAST") - if (tr is QueryCloneable<*>) { - tr = tr.cloneSelf() as Transformable - } - return tr.offset(offset).limit(limit) - } -} - -infix fun Transformable.groupBy(nameAlias: NameAlias): Where = groupBy(nameAlias) - -infix fun Transformable.groupBy(property: IProperty<*>): Where = groupBy(property) - -infix fun Transformable.having(sqlOperator: SQLOperator): Where = having(sqlOperator) \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/Trigger.kt b/lib/src/main/kotlin/com/dbflow5/query/Trigger.kt deleted file mode 100644 index 165158a8d13966ac2b2943a325ba3e74be84e55c..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/Trigger.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.dbflow5.query - -import com.dbflow5.appendOptional -import com.dbflow5.appendQuotedIfNeeded -import com.dbflow5.query.property.IProperty -import com.dbflow5.sql.Query -import kotlin.reflect.KClass - -/** - * Description: Describes an easy way to create a SQLite TRIGGER - */ -class Trigger -/** - * Creates a trigger with the specified trigger name. You need to complete - * the trigger using - * - * @param name What we should call this trigger - */ -private constructor( - /** - * The name in the DB - */ - /** - * @return The name of this TRIGGER - */ - val name: String) : Query { - - /** - * If it's [.BEFORE], [.AFTER], or [.INSTEAD_OF] - */ - private var beforeOrAfter: String = "" - - private var temporary: Boolean = false - - override val query: String - get() { - val queryBuilder = StringBuilder("CREATE ") - if (temporary) { - queryBuilder.append("TEMP ") - } - queryBuilder.append("TRIGGER IF NOT EXISTS ") - .appendQuotedIfNeeded(name).append(" ") - .appendOptional("$beforeOrAfter ") - - return queryBuilder.toString() - } - - /** - * Sets the trigger as temporary. - */ - fun temporary() = apply { - this.temporary = true - } - - /** - * Specifies AFTER eventName - */ - fun after() = apply { - beforeOrAfter = AFTER - } - - /** - * Specifies BEFORE eventName - */ - fun before() = apply { - beforeOrAfter = BEFORE - } - - /** - * Specifies INSTEAD OF eventName - */ - fun insteadOf() = apply { - beforeOrAfter = INSTEAD_OF - } - - /** - * Starts a DELETE ON command - * - * @param onTable The table ON - */ - infix fun deleteOn(onTable: Class): TriggerMethod = - TriggerMethod(this, TriggerMethod.DELETE, onTable) - - /** - * Starts a INSERT ON command - * - * @param onTable The table ON - */ - infix fun insertOn(onTable: Class): TriggerMethod = - TriggerMethod(this, TriggerMethod.INSERT, onTable) - - /** - * Starts an UPDATE ON command - * - * @param onTable The table ON - * @param properties if empty, will not execute an OF command. If you specify columns, - * the UPDATE OF column1, column2,... will be used. - */ - fun updateOn(onTable: Class, vararg properties: IProperty<*>): TriggerMethod = - TriggerMethod(this, TriggerMethod.UPDATE, onTable, *properties) - - companion object { - - /** - * Specifies that we should do this TRIGGER before some event - */ - @JvmField - val BEFORE = "BEFORE" - - /** - * Specifies that we should do this TRIGGER after some event - */ - @JvmField - val AFTER = "AFTER" - - /** - * Specifies that we should do this TRIGGER instead of the specified events - */ - @JvmField - val INSTEAD_OF = "INSTEAD OF" - - /** - * @param triggerName The name of the trigger to use. - * @return A new trigger. - */ - @JvmStatic - fun create(triggerName: String) = Trigger(triggerName) - } -} - -infix fun Trigger.deleteOn(kClass: KClass) = deleteOn(kClass.java) - -infix fun Trigger.insertOn(kClass: KClass) = insertOn(kClass.java) - -infix fun Trigger.updateOn(kClass: KClass) = updateOn(kClass.java) \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/TriggerMethod.kt b/lib/src/main/kotlin/com/dbflow5/query/TriggerMethod.kt deleted file mode 100644 index 21e3be1e92a2d50a4f22ed263f0f7157e9a5c736..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/TriggerMethod.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.dbflow5.query - -import com.dbflow5.appendArray -import com.dbflow5.config.FlowManager -import com.dbflow5.query.property.IProperty -import com.dbflow5.sql.Query - -/** - * Description: Describes the method that the trigger uses. - */ -class TriggerMethod -internal constructor(internal val trigger: Trigger, private val methodName: String, - internal var onTable: Class, vararg properties: IProperty<*>) : Query { - private var properties: List> = arrayListOf() - private var forEachRow = false - private var whenCondition: SQLOperator? = null - - override val query: String - get() { - val queryBuilder = StringBuilder(trigger.query) - .append(methodName) - if (properties.isNotEmpty()) { - queryBuilder.append(" OF ") - .appendArray(properties.toTypedArray()) - } - queryBuilder.append(" ON ").append(FlowManager.getTableName(onTable)) - - if (forEachRow) { - queryBuilder.append(" FOR EACH ROW ") - } - - whenCondition?.let { whenCondition -> - queryBuilder.append(" WHEN ") - whenCondition.appendConditionToQuery(queryBuilder) - queryBuilder.append(" ") - } - - queryBuilder.append(" ") - - return queryBuilder.toString() - } - - init { - if (properties.isNotEmpty() && properties.getOrNull(0) != null) { - if (methodName != UPDATE) { - throw IllegalArgumentException("An Trigger OF can only be used with an UPDATE method") - } - this.properties = properties.toList() - } - } - - fun forEachRow() = apply { - forEachRow = true - } - - /** - * Appends a WHEN condition after the ON name and before BEGIN...END - * - * @param condition The condition for the trigger - * @return - */ - @JvmName("when") - fun whenever(condition: SQLOperator) = apply { - whenCondition = condition - } - - /** - * Specify the logic that gets executed for this trigger. Supported statements include: - * [Update], INSERT, [Delete], - * and [Select] - * - * @param triggerLogicQuery The query to run for the BEGIN..END of the trigger - * @return This trigger - */ - infix fun begin(triggerLogicQuery: Query): CompletedTrigger = - CompletedTrigger(this, triggerLogicQuery) - - companion object { - - const val DELETE = "DELETE" - const val INSERT = "INSERT" - const val UPDATE = "UPDATE" - - @JvmField - val METHODS = listOf(INSERT, UPDATE, DELETE) - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/UnSafeStringOperator.kt b/lib/src/main/kotlin/com/dbflow5/query/UnSafeStringOperator.kt deleted file mode 100644 index 41cdd010a63a152f65dba5452e83053cbd0b3808..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/UnSafeStringOperator.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.dbflow5.query - -import com.dbflow5.isNotNullOrEmpty -import com.dbflow5.sql.Query - -/** - * Description: This class will use a String to describe its condition. - * Not recommended for normal queries, but can be used as a fall-back. - */ -class UnSafeStringOperator(selection: String, selectionArgs: Array) : SQLOperator, Query { - - private val conditionString: String? - private var separator = "" - - override val query: String - get() = appendToQuery() - - init { - var newSelection: String? = selection - // replace question marks in order - if (newSelection != null) { - for (selectionArg in selectionArgs) { - newSelection = newSelection?.replaceFirst("\\?".toRegex(), selectionArg) - } - } - this.conditionString = newSelection - } - - override fun appendConditionToQuery(queryBuilder: StringBuilder) { - queryBuilder.append(conditionString) - } - - override fun columnName(): String = "" - - override fun separator(): String? = separator - - override fun separator(separator: String) = apply { - this.separator = separator - } - - override fun hasSeparator(): Boolean = separator.isNotNullOrEmpty() - - override fun operation(): String = "" - - override fun value(): Any? = "" -} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/Update.kt b/lib/src/main/kotlin/com/dbflow5/query/Update.kt deleted file mode 100644 index b9b1feb319e82a3a7480e0477aae92c4823a458f..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/Update.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.dbflow5.query - -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.config.FlowManager -import com.dbflow5.sql.Query -import com.dbflow5.sql.QueryCloneable - -/** - * Description: The SQLite UPDATE query. Will update rows in the DB. - */ -class Update -/** - * Constructs new instace of an UPDATE query with the specified table. - * - * @param table The table to use. - */ -internal constructor(val table: Class) : Query, QueryCloneable> { - - /** - * The conflict action to resolve updates. - */ - private var conflictAction: ConflictAction = ConflictAction.NONE - - override val query: String - get() { - val queryBuilder = StringBuilder("UPDATE ") - if (conflictAction != ConflictAction.NONE) { - queryBuilder.append("OR").append(" ${conflictAction.name} ") - } - queryBuilder.append(FlowManager.getTableName(table)).append(" ") - return queryBuilder.toString() - } - - override fun cloneSelf(): Update = Update(table) - .conflictAction(conflictAction) - - fun conflictAction(conflictAction: ConflictAction) = apply { - this.conflictAction = conflictAction - } - - fun or(conflictAction: ConflictAction) = conflictAction(conflictAction) - - /** - * @return This instance. - * @see ConflictAction.ROLLBACK - */ - fun orRollback() = conflictAction(ConflictAction.ROLLBACK) - - /** - * @return This instance. - * @see ConflictAction.ABORT - */ - fun orAbort() = conflictAction(ConflictAction.ABORT) - - /** - * @return This instance. - * @see ConflictAction.REPLACE - */ - fun orReplace() = conflictAction(ConflictAction.REPLACE) - - /** - * @return This instance. - * @see ConflictAction.FAIL - */ - fun orFail() = conflictAction(ConflictAction.FAIL) - - /** - * @return This instance. - * @see ConflictAction.IGNORE - */ - fun orIgnore() = conflictAction(ConflictAction.IGNORE) - - /** - * Begins a SET piece of the SQL query - * - * @param conditions The array of conditions that define this SET statement - * @return A SET query piece of this statement - */ - fun set(vararg conditions: SQLOperator): Set = Set(this, table) - .conditions(*conditions) -} - - -infix fun Update.set(sqlOperator: SQLOperator) = set(sqlOperator) \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/query/Where.kt b/lib/src/main/kotlin/com/dbflow5/query/Where.kt deleted file mode 100644 index 479c045b392f1d83c20ab0a8a104718e54fe61d8..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/Where.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.dbflow5.query - -import com.dbflow5.appendQualifier -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.property.IProperty -import com.dbflow5.sql.QueryCloneable -import com.dbflow5.structure.ChangeAction - -/** - * Description: Defines the SQL WHERE statement of the query. - */ -class Where -/** - * Constructs this class with the specified [com.dbflow5.config.FlowManager] - * and [From] chunk - * - * @param whereBase The FROM or SET statement chunk - */ -internal constructor( - /** - * The first chunk of the SQL statement before this query. - */ - val whereBase: WhereBase, vararg conditions: SQLOperator) - : BaseModelQueriable(whereBase.table), - ModelQueriable, Transformable, QueryCloneable> { - - /** - * Helps to build the where statement easily - */ - private val operatorGroup: OperatorGroup = OperatorGroup.nonGroupingClause() - - private val groupByList = arrayListOf() - - private val orderByList = arrayListOf() - - /** - * The SQL HAVING statement - */ - private val havingGroup: OperatorGroup = OperatorGroup.nonGroupingClause() - - private var limit = VALUE_UNSET - private var offset = VALUE_UNSET - - override val primaryAction: ChangeAction - get() = whereBase.primaryAction - - override fun cloneSelf(): Where { - val where = Where(whereBase.cloneSelf(), *operatorGroup.conditions.toTypedArray()) - where.groupByList.addAll(groupByList) - where.orderByList.addAll(orderByList) - where.havingGroup.andAll(havingGroup.conditions) - where.limit = limit - where.offset = offset - return where - } - - override val query: String - get() { - val fromQuery = whereBase.query.trim { it <= ' ' } - val queryBuilder = StringBuilder(fromQuery).append(" ") - .appendQualifier("WHERE", operatorGroup.query) - .appendQualifier("GROUP BY", groupByList.joinToString(separator = ",")) - .appendQualifier("HAVING", havingGroup.query) - .appendQualifier("ORDER BY", orderByList.joinToString(separator = ",")) - - if (limit > VALUE_UNSET) { - queryBuilder.appendQualifier("LIMIT", limit.toString()) - } - if (offset > VALUE_UNSET) { - queryBuilder.appendQualifier("OFFSET", offset.toString()) - } - - return queryBuilder.toString() - } - - init { - operatorGroup.andAll(*conditions) - } - - /** - * Joins the [SQLOperator] by the prefix of "AND" (unless its the first condition). - */ - infix fun and(condition: SQLOperator) = apply { - operatorGroup.and(condition) - } - - /** - * Joins the [SQLOperator] by the prefix of "OR" (unless its the first condition). - */ - infix fun or(condition: SQLOperator) = apply { - operatorGroup.or(condition) - } - - /** - * Joins all of the [SQLOperator] by the prefix of "AND" (unless its the first condition). - */ - fun andAll(conditions: List) = apply { - operatorGroup.andAll(conditions) - } - - /** - * Joins all of the [SQLOperator] by the prefix of "AND" (unless its the first condition). - */ - fun andAll(vararg conditions: SQLOperator) = apply { - operatorGroup.andAll(*conditions) - } - - override fun groupBy(vararg nameAliases: NameAlias) = apply { - groupByList.addAll(nameAliases.toList()) - } - - override fun groupBy(vararg properties: IProperty<*>) = apply { - properties.mapTo(groupByList) { it.nameAlias } - } - - /** - * Defines a SQL HAVING statement without the HAVING. - * - * @param conditions The array of [SQLOperator] - * @return - */ - override fun having(vararg conditions: SQLOperator) = apply { - havingGroup.andAll(*conditions) - } - - override fun orderBy(nameAlias: NameAlias, ascending: Boolean) = apply { - orderByList.add(OrderBy.fromNameAlias(nameAlias, ascending)) - } - - override fun orderBy(property: IProperty<*>, ascending: Boolean) = apply { - orderByList.add(OrderBy.fromProperty(property, ascending)) - } - - override fun orderBy(orderBy: OrderBy) = apply { - orderByList.add(orderBy) - } - - /** - * For use in ContentProvider generation. Appends all ORDER BY here. - * - * @param orderByList The order by. - * @return this instance. - */ - override fun orderByAll(orderByList: List) = apply { - this.orderByList.addAll(orderByList) - } - - override fun limit(count: Long) = apply { - this.limit = count - } - - override fun offset(offset: Long) = apply { - this.offset = offset - } - - /** - * Specify that we use an EXISTS statement for this Where class. - * - * @param where The query to use in the EXISTS clause. Such as SELECT * FROM `MyTable` WHERE ... etc. - * @return This where with an EXISTS clause. - */ - fun exists(where: Where<*>) = apply { - operatorGroup.and(ExistenceOperator(where)) - } - - /** - * @return the result of the query as a [FlowCursor]. - */ - override fun cursor(databaseWrapper: DatabaseWrapper): FlowCursor? =// Query the sql here - when { - whereBase.queryBuilderBase is Select -> databaseWrapper.rawQuery(query, null) - else -> super.cursor(databaseWrapper) - } - - /** - * Queries for all of the results this statement returns from a DB cursor in the form of the [T] - * - * @return All of the entries in the DB converted into [T] - */ - override fun queryList(databaseWrapper: DatabaseWrapper): MutableList { - checkSelect("cursor") - return super.queryList(databaseWrapper) - } - - /** - * Queries and returns only the first [T] result from the DB. Will enforce a limit of 1 item - * returned from the database. - * - * @return The first result of this query. Note: this cursor forces a limit of 1 from the database. - */ - override fun querySingle(databaseWrapper: DatabaseWrapper): T? { - checkSelect("cursor") - limit(1) - return super.querySingle(databaseWrapper) - } - - private fun checkSelect(methodName: String) { - if (whereBase.queryBuilderBase !is Select) { - throw IllegalArgumentException("Please use $methodName(). The beginning is not a ISelect") - } - } - - companion object { - - private val VALUE_UNSET = -1L - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/cache/ModelLruCache.kt b/lib/src/main/kotlin/com/dbflow5/query/cache/ModelLruCache.kt deleted file mode 100644 index 03992b3d56f62cfe5b0c6040ef057e26f05698f2..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/cache/ModelLruCache.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.dbflow5.query.cache - -import android.util.LruCache -import com.dbflow5.annotation.DEFAULT_CACHE_SIZE - -/** - * Description: Provides an [android.util.LruCache] under its hood - * and provides synchronization mechanisms. - */ -class ModelLruCache(size: Int) - : ModelCache>(LruCache(size)) { - - override fun addModel(id: Any?, model: TModel) { - throwIfNotNumber(id) { - synchronized(cache) { - cache.put(it.toLong(), model) - } - } - } - - override fun removeModel(id: Any): TModel? = throwIfNotNumber(id) { - synchronized(cache) { - cache.remove(it.toLong()) - } - } - - override fun clear() { - synchronized(cache) { - cache.evictAll() - } - } - - override fun setCacheSize(size: Int) { - cache.resize(size) - } - - override fun get(id: Any?): TModel? = throwIfNotNumber(id) { cache[it.toLong()] } - - private inline fun throwIfNotNumber(id: Any?, fn: (Number) -> R) = - if (id is Number) { - fn(id) - } else { - throw IllegalArgumentException("A ModelLruCache must use an id that can cast to" - + "a Number to convert it into a long") - } - - companion object { - - /** - * @param size The size, if less than or equal to 0 we set it to [DEFAULT_CACHE_SIZE]. - */ - fun newInstance(size: Int): ModelLruCache { - var locSize = size - if (locSize <= 0) { - locSize = DEFAULT_CACHE_SIZE - } - return ModelLruCache(locSize) - } - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/cache/SparseArrayBasedCache.kt b/lib/src/main/kotlin/com/dbflow5/query/cache/SparseArrayBasedCache.kt deleted file mode 100644 index 63357f01b34c2ca0f2a32e6bfde29028c745ee81..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/cache/SparseArrayBasedCache.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.dbflow5.query.cache - -import android.util.SparseArray - -import com.dbflow5.config.FlowLog - -/** - * Description: A cache backed by a [android.util.SparseArray] - */ -class SparseArrayBasedCache : ModelCache> { - - /** - * Constructs new instance with a [SparseArray] cache - */ - constructor() : super(SparseArray()) - - /** - * Constructs new instance with a [android.util.SparseArray] cache - * - * @param initialCapacity The initial capacity of the sparse array - */ - constructor(initialCapacity: Int) : super(SparseArray(initialCapacity)) - - constructor(sparseArray: SparseArray) : super(sparseArray) - - override fun addModel(id: Any?, model: TModel) { - if (id is Number) { - synchronized(cache) { - cache.put(id.toInt(), model) - } - } else { - throw IllegalArgumentException("A SparseArrayBasedCache must use an id that can cast to " + "a Number to convert it into a int") - } - } - - override fun removeModel(id: Any): TModel? { - val model = get(id) - synchronized(cache) { - cache.remove((id as Number).toInt()) - } - return model - } - - override fun clear() { - synchronized(cache) { - cache.clear() - } - } - - override fun setCacheSize(size: Int) { - FlowLog.log(FlowLog.Level.I, "The cache size for SparseArrayBasedCache is not re-configurable.") - } - - override fun get(id: Any?): TModel? { - return if (id is Number) { - cache.get(id.toInt()) - } else { - throw IllegalArgumentException("A SparseArrayBasedCache uses an id that can cast to " - + "a Number to convert it into a int") - } - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorIterator.kt b/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorIterator.kt deleted file mode 100644 index 3b8c45af360f708743d92728c18cc8171791b6e6..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorIterator.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.dbflow5.query.list - -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.ModelQueriable -import com.dbflow5.query.Transformable -import com.dbflow5.sql.QueryCloneable -import java.io.Closeable - -/** - * Description: Provides iteration capabilities to a [FlowCursorList]. - */ -class FlowCursorIterator -@JvmOverloads constructor( - databaseWrapper: DatabaseWrapper, - cursorList: IFlowCursorIterator, - startingLocation: Long, - private var count: Long = cursorList.count - startingLocation) - : ListIterator, AutoCloseable, Closeable { - private var reverseIndex: Long = 0 - private var startingCount: Long = 0 - private val cursorList: IFlowCursorIterator - - constructor(databaseWrapper: DatabaseWrapper, - cursorList: IFlowCursorIterator) : this(databaseWrapper, cursorList, 0, cursorList.count) - - init { - var newCursorList = cursorList - var newStartingLocation = startingLocation - if (!cursorList.trackingCursor) { - // no cursor specified, we can optimize the query to return results within SQL range, rather than all rows. - @Suppress("UNCHECKED_CAST") - val _cursorList = when (cursorList) { - is FlowCursorList<*> -> cursorList as FlowCursorList - is FlowQueryList<*> -> cursorList.internalCursorList as FlowCursorList - else -> throw IllegalArgumentException("The specified ${IFlowCursorIterator::class.java.simpleName} " + - "must track cursor unless it is a FlowCursorList or FlowQueryList") - } - val modelQueriable = _cursorList.modelQueriable - if (modelQueriable is Transformable<*>) { - @Suppress("UNCHECKED_CAST") - newCursorList = (modelQueriable as Transformable) - .constrain(startingLocation, count) - .cursorList(databaseWrapper) - this.count = newCursorList.count - newStartingLocation = 0 - } - } - newCursorList.cursor?.let { cursor -> - // request larger than actual count. Can almost never be long, but we keep precision. - if (this.count > cursor.count - newStartingLocation) { - this.count = cursor.count - newStartingLocation - } - - cursor.moveToPosition(newStartingLocation.toInt() - 1) - startingCount = newCursorList.count - reverseIndex = this.count - reverseIndex -= newStartingLocation - - if (reverseIndex < 0) { - reverseIndex = 0 - } - } - this.cursorList = newCursorList - } - - val isClosed - get() = cursorList.isClosed - - @Throws(Exception::class) - override fun close() { - cursorList.close() - } - - override fun hasNext(): Boolean { - checkSizes() - return reverseIndex > 0 - } - - override fun hasPrevious(): Boolean { - checkSizes() - return reverseIndex < count - } - - override fun next(): T { - checkSizes() - val item = cursorList[count - reverseIndex] - reverseIndex-- - return item - } - - override fun nextIndex(): Int = (reverseIndex + 1).toInt() - - override fun previous(): T { - checkSizes() - val item = cursorList[count - reverseIndex] - reverseIndex++ - return item - } - - override fun previousIndex(): Int = reverseIndex.toInt() - - private fun checkSizes() { - if (startingCount != cursorList.count) { - throw RuntimeException("Concurrent Modification: Cannot change Cursor data " + - "during iteration. Expected $startingCount, found: ${cursorList.count}") - } - } - - private fun getQueriableFromParams(transformable: Transformable, - startPosition: Long, max: Long): ModelQueriable { - var tr: Transformable = transformable - @Suppress("UNCHECKED_CAST") - if (tr is QueryCloneable<*>) { - tr = tr.cloneSelf() as Transformable - } - return tr.offset(startPosition).limit(max) - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorList.kt b/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorList.kt deleted file mode 100644 index 0bfedb165c56b3fb32ff3239d3529cce3d5abd34..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/list/FlowCursorList.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.dbflow5.query.list - -import android.widget.ListView -import com.dbflow5.adapter.RetrievalAdapter -import com.dbflow5.config.FlowLog -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.ModelQueriable - -/** - * Interface for callbacks when cursor gets refreshed. - */ -typealias OnCursorRefreshListener = (cursorList: FlowCursorList) -> Unit - -/** - * Description: A non-modifiable, cursor-backed list that you can use in [ListView] or other data sources. - */ -class FlowCursorList private constructor(builder: Builder) : IFlowCursorIterator { - - val table: Class - val modelQueriable: ModelQueriable - private var _cursor: FlowCursor? = null - private val cursorFunc: () -> FlowCursor - val databaseWrapper: DatabaseWrapper - - override var trackingCursor: Boolean = false - private set - - internal val instanceAdapter: RetrievalAdapter - - private val cursorRefreshListenerSet = hashSetOf>() - - /** - * @return the full, converted [T] list from the database on this list. For large - * data sets that require a large conversion, consider calling this on a BG thread. - */ - val all: List - get() { - unpackCursor() - throwIfCursorClosed() - warnEmptyCursor() - return _cursor?.let { cursor -> - instanceAdapter.listModelLoader.convertToData(cursor, databaseWrapper) - } ?: listOf() - } - - /** - * @return the count of rows on this database query list. - */ - val isEmpty: Boolean - get() { - throwIfCursorClosed() - warnEmptyCursor() - return count == 0L - } - - init { - table = builder.modelClass - this.modelQueriable = builder.modelQueriable - this.databaseWrapper = builder.databaseWrapper - trackingCursor = builder.cursor != null - cursorFunc = { - builder.cursor - ?: modelQueriable.cursor(databaseWrapper) - ?: throw IllegalStateException("The object must evaluate to a cursor") - } - instanceAdapter = FlowManager.getRetrievalAdapter(builder.modelClass) - } - - override val isClosed: Boolean - get() = _cursor?.isClosed ?: true - - override operator fun iterator(): FlowCursorIterator = FlowCursorIterator(databaseWrapper, this) - - override fun iterator(startingLocation: Long, limit: Long): FlowCursorIterator = - FlowCursorIterator(databaseWrapper, this, startingLocation, limit) - - /** - * Register listener for when cursor refreshes. - */ - fun addOnCursorRefreshListener(onCursorRefreshListener: OnCursorRefreshListener) { - synchronized(cursorRefreshListenerSet) { - cursorRefreshListenerSet.add(onCursorRefreshListener) - } - } - - fun removeOnCursorRefreshListener(onCursorRefreshListener: OnCursorRefreshListener) { - synchronized(cursorRefreshListenerSet) { - cursorRefreshListenerSet.remove(onCursorRefreshListener) - } - } - - /** - * Refreshes the data backing this list, and destroys the Model cache. - */ - @Synchronized - fun refresh() { - val cursor = unpackCursor() - cursor.close() - this._cursor = modelQueriable.cursor(databaseWrapper) - trackingCursor = false - synchronized(cursorRefreshListenerSet) { - cursorRefreshListenerSet.forEach { listener -> listener(this) } - } - } - - /** - * Returns a model at the specified index. If we are using the cache and it does not contain a model - * at that index, we move the cursor to the specified index and construct the [T]. - * - * @param index The row number in the [FlowCursor] to look at - * @return The [T] converted from the cursor - */ - override fun get(index: Long): T { - throwIfCursorClosed() - - val cursor = unpackCursor() - return if (cursor.moveToPosition(index.toInt())) { - instanceAdapter.singleModelLoader.convertToData( - FlowCursor.from(cursor), false, - databaseWrapper) - ?: throw IndexOutOfBoundsException("Invalid item at index $index. Check your cursor data.") - } else { - throw IndexOutOfBoundsException("Invalid item at index $index. Check your cursor data.") - } - } - - /** - * @return the count of the rows in the [FlowCursor] backed by this list. - */ - override val count: Long - get() { - unpackCursor() - throwIfCursorClosed() - warnEmptyCursor() - return (_cursor?.count ?: 0).toLong() - } - - /** - * Closes the cursor backed by this list - */ - override fun close() { - warnEmptyCursor() - _cursor?.close() - _cursor = null - } - - override val cursor: FlowCursor? - get() { - unpackCursor() - throwIfCursorClosed() - warnEmptyCursor() - return _cursor - } - - private fun unpackCursor(): FlowCursor = _cursor ?: cursorFunc().also { _cursor = it } - - private fun throwIfCursorClosed() { - if (_cursor?.isClosed == true) { - throw IllegalStateException("Cursor has been closed for FlowCursorList") - } - } - - private fun warnEmptyCursor() { - if (_cursor == null) { - FlowLog.log(FlowLog.Level.W, "Cursor was null for FlowCursorList") - } - } - - /** - * @return A new [Builder] that contains the same cache, query statement, and other - * underlying data, but allows for modification. - */ - fun newBuilder(): Builder = Builder(modelQueriable, databaseWrapper).cursor(_cursor) - - /** - * Provides easy way to construct a [FlowCursorList]. - * - * @param [T] - */ - class Builder(internal var modelQueriable: ModelQueriable, - internal val databaseWrapper: DatabaseWrapper) { - - internal val modelClass: Class = modelQueriable.table - internal var cursor: FlowCursor? = null - - fun cursor(cursor: FlowCursor?) = apply { - this.cursor = cursor - } - - fun build() = FlowCursorList(this) - - } - -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/list/FlowQueryList.kt b/lib/src/main/kotlin/com/dbflow5/query/list/FlowQueryList.kt deleted file mode 100644 index e275793f6635eb08f09095e10de011b6e387d5b4..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/list/FlowQueryList.kt +++ /dev/null @@ -1,218 +0,0 @@ -package com.dbflow5.query.list - -import android.os.Handler -import android.os.Looper -import com.dbflow5.adapter.RetrievalAdapter -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.ModelQueriable - -/** - * Description: A query-backed immutable [List]. Represents the results of a cursor without loading - * the full query out into an actual [List]. Avoid keeping this class around without calling [close] as - * it leaves a [FlowCursor] object active. - */ -class FlowQueryList( - /** - * Holds the table cursor - */ - val internalCursorList: FlowCursorList, - private val refreshHandler: Handler = globalRefreshHandler) - : List, IFlowCursorIterator by internalCursorList { - - private var pendingRefresh = false - - /** - * @return a mutable list that does not reflect changes on the underlying DB. - */ - val copy: List - get() = internalCursorList.all - - internal val retrievalAdapter: RetrievalAdapter - get() = internalCursorList.instanceAdapter - - override val size: Int - get() = internalCursorList.count.toInt() - - private val refreshRunnable = object : Runnable { - override fun run() { - synchronized(this) { - pendingRefresh = false - } - refresh() - } - } - - internal constructor(builder: Builder) : this( - internalCursorList = FlowCursorList.Builder(builder.modelQueriable, builder.databaseWrapper) - .cursor(builder.cursor) - .build(), - refreshHandler = builder.refreshHandler - ) - - - fun addOnCursorRefreshListener(onCursorRefreshListener: OnCursorRefreshListener) { - internalCursorList.addOnCursorRefreshListener(onCursorRefreshListener) - } - - fun removeOnCursorRefreshListener(onCursorRefreshListener: OnCursorRefreshListener) { - internalCursorList.removeOnCursorRefreshListener(onCursorRefreshListener) - } - - val cursorList: FlowCursorList - get() = internalCursorList - - /** - * @return Constructs a new [Builder] that reuses the underlying [FlowCursor], - * callbacks, and other properties. - */ - fun newBuilder(): Builder = Builder(internalCursorList) - - /** - * Refreshes the content backing this list. - */ - fun refresh() { - internalCursorList.refresh() - } - - /** - * Will refresh content at a slightly later time, and multiple subsequent calls to this method within - * a short period of time will be combined into one call. - */ - fun refreshAsync() { - synchronized(this) { - if (pendingRefresh) { - return - } - pendingRefresh = true - } - refreshHandler.post(refreshRunnable) - } - - - /** - * Checks to see if the table contains the object only if its a [T] - * - * @param element A model class. For interface purposes, this must be an Object. - * @return always false if its anything other than the current table. True if [com.dbflow5.structure.Model.exists] passes. - */ - override operator fun contains(element: T): Boolean { - return internalCursorList.instanceAdapter.exists(element, internalCursorList.databaseWrapper) - } - - /** - * If the collection is null or empty, we return false. - * - * @param elements The collection to check if all exist within the table. - * @return true if all items exist in table, false if at least one fails. - */ - override fun containsAll(elements: Collection): Boolean { - var contains = !elements.isEmpty() - if (contains) { - contains = elements.all { it in this } - } - return contains - } - - /** - * Returns the item from the backing [FlowCursorList]. First call - * will load the model from the cursor, while subsequent calls will use the cache. - * - * @param index the row from the internal [FlowCursorList] query that we use. - * @return A model converted from the internal [FlowCursorList]. For - * performance improvements, ensure caching is turned on. - */ - override fun get(index: Long): T = internalCursorList[index] - - override operator fun get(index: Int): T = internalCursorList[index.toLong()] - - override fun indexOf(element: T): Int { - throw UnsupportedOperationException( - "We cannot determine which index in the table this item exists at efficiently") - } - - override fun isEmpty(): Boolean { - return internalCursorList.isEmpty - } - - /** - * @return An iterator that loops through all items in the list. - * Be careful as this method will convert all data under this table into a list of [T] in the UI thread. - */ - override fun iterator(): FlowCursorIterator { - return FlowCursorIterator(internalCursorList.databaseWrapper, this) - } - - override fun iterator(startingLocation: Long, limit: Long): FlowCursorIterator { - return FlowCursorIterator(internalCursorList.databaseWrapper, this, startingLocation, limit) - } - - override fun lastIndexOf(element: T): Int { - throw UnsupportedOperationException( - "We cannot determine which index in the table this item exists at efficiently") - } - - /** - * @return A list iterator that loops through all items in the list. - * Be careful as this method will convert all data under this table into a list of [T] in the UI thread. - */ - override fun listIterator(): ListIterator { - return FlowCursorIterator(internalCursorList.databaseWrapper, this) - } - - /** - * @param index The index to start the iterator. - * @return A list iterator that loops through all items in the list. - * Be careful as this method will convert all data under this table into a list of [T] in the UI thread. - */ - override fun listIterator(index: Int): ListIterator { - return FlowCursorIterator(internalCursorList.databaseWrapper, this, index.toLong()) - } - - override fun subList(fromIndex: Int, toIndex: Int): List { - val tableList = internalCursorList.all - return tableList.subList(fromIndex, toIndex) - } - - class Builder { - - internal val table: Class - - internal var cursor: FlowCursor? = null - internal var modelQueriable: ModelQueriable - internal val databaseWrapper: DatabaseWrapper - internal val refreshHandler: Handler - - internal constructor(cursorList: FlowCursorList, - refreshHandler: Handler = globalRefreshHandler) { - this.databaseWrapper = cursorList.databaseWrapper - table = cursorList.table - cursor = cursorList.cursor - modelQueriable = cursorList.modelQueriable - this.refreshHandler = refreshHandler - } - - constructor(modelQueriable: ModelQueriable, - databaseWrapper: DatabaseWrapper, - refreshHandler: Handler = globalRefreshHandler - ) { - this.databaseWrapper = databaseWrapper - this.table = modelQueriable.table - this.modelQueriable = modelQueriable - this.refreshHandler = refreshHandler - } - - fun cursor(cursor: FlowCursor) = apply { - this.cursor = cursor - } - - fun build() = FlowQueryList(this) - } - - companion object { - - private val globalRefreshHandler = Handler(Looper.myLooper()) - } - - -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/property/IndexProperty.kt b/lib/src/main/kotlin/com/dbflow5/query/property/IndexProperty.kt deleted file mode 100644 index ab191f2ea4c3f9e369ee21fa1fb8f180d95f193f..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/property/IndexProperty.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.dbflow5.query.property - -import com.dbflow5.annotation.Table -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.Index -import com.dbflow5.quoteIfNeeded - -/** - * Description: Defines an INDEX in Sqlite. It basically speeds up data retrieval over large datasets. - * It gets generated from [Table.indexGroups], but also can be manually constructed. These are activated - * and deactivated manually. - */ -class IndexProperty(indexName: String, - private val unique: Boolean, - private val table: Class, - vararg properties: IProperty<*>) { - - @Suppress("UNCHECKED_CAST") - private val properties: Array> = properties as Array> - - val index: Index - get() = Index(indexName, table).on(*properties).unique(unique) - - val indexName = indexName.quoteIfNeeded() ?: "" - - fun createIfNotExists(wrapper: DatabaseWrapper) = index.createIfNotExists(wrapper) - - fun drop(wrapper: DatabaseWrapper) = index.drop(wrapper) -} diff --git a/lib/src/main/kotlin/com/dbflow5/query/property/Property.kt b/lib/src/main/kotlin/com/dbflow5/query/property/Property.kt deleted file mode 100644 index 186e40528dcf864f1e4d27592feae441948a7b95..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/property/Property.kt +++ /dev/null @@ -1,269 +0,0 @@ -package com.dbflow5.query.property - -import com.dbflow5.config.FlowManager -import com.dbflow5.query.BaseModelQueriable -import com.dbflow5.query.IConditional -import com.dbflow5.query.IOperator -import com.dbflow5.query.NameAlias -import com.dbflow5.query.Operator -import com.dbflow5.query.OrderBy - -/** - * Description: The main, immutable property class that gets generated from a table definition. - * - * - * This class delegates all of its [IOperator] methods to a new [Operator] that's used - * in the SQLite query language. - * - * - * This ensures that the language is strictly type-safe and only declared - * columns get used. Also any calls on the methods return a new [Property]. - * - * - * This is type parametrized so that all values passed to this class remain properly typed. - */ -open class Property(override val table: Class<*>?, - override val nameAlias: NameAlias) - : IProperty>, IOperator { - - val definition: String - get() = nameAlias.fullQuery - - /** - * @return helper method to construct it in a [.distinct] call. - */ - private val distinctAliasName: NameAlias - get() = nameAlias - .newBuilder() - .distinct() - .build() - - protected open val operator: Operator - get() = Operator.op(nameAlias) - - constructor(table: Class<*>?, columnName: String) : this(table, NameAlias.Builder(columnName).build()) - - constructor(table: Class<*>?, columnName: String, aliasName: String) - : this(table, NameAlias.builder(columnName).`as`(aliasName).build()) - - override fun withTable(): Property = Property(table, nameAlias - .newBuilder() - .withTable(FlowManager.getTableName(table!!)) - .build()) - - override val query: String - get() = nameAlias.query - - override fun toString(): String = nameAlias.toString() - - override fun `is`(conditional: IConditional): Operator<*> = operator.`is`(conditional) - - override fun eq(conditional: IConditional): Operator<*> = operator.eq(conditional) - - override fun isNot(conditional: IConditional): Operator<*> = operator.isNot(conditional) - - override fun notEq(conditional: IConditional): Operator<*> = operator.notEq(conditional) - - override fun like(conditional: IConditional): Operator<*> = operator.like(conditional) - - override fun glob(conditional: IConditional): Operator<*> = operator.glob(conditional) - - override fun like(value: String): Operator = operator.like(value) - - override fun match(value: String): Operator<*> = operator.match(value) - - override fun notLike(value: String): Operator = operator.notLike(value) - - override fun glob(value: String): Operator = operator.glob(value) - - override fun greaterThan(conditional: IConditional): Operator<*> = - operator.greaterThan(conditional) - - override fun greaterThanOrEq(conditional: IConditional): Operator<*> = - operator.greaterThanOrEq(conditional) - - override fun lessThan(conditional: IConditional): Operator<*> = operator.lessThan(conditional) - - override fun lessThanOrEq(conditional: IConditional): Operator<*> = - operator.lessThanOrEq(conditional) - - override fun between(conditional: IConditional): Operator.Between<*> = - operator.between(conditional) - - override fun `in`(firstConditional: IConditional, vararg conditionals: IConditional): Operator.In<*> = - operator.`in`(firstConditional, *conditionals) - - override fun notIn(firstConditional: IConditional, vararg conditionals: IConditional): Operator.In<*> = - operator.notIn(firstConditional, *conditionals) - - override fun `is`(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.`is`(baseModelQueriable) - - override fun isNull(): Operator<*> = operator.isNull() - - override fun eq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.eq(baseModelQueriable) - - override fun isNot(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.isNot(baseModelQueriable) - - override fun isNotNull(): Operator<*> = operator.isNotNull() - - override fun notEq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.notEq(baseModelQueriable) - - override fun like(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.like(baseModelQueriable) - - override fun notLike(conditional: IConditional): Operator<*> = operator.notLike(conditional) - - override fun notLike(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.notLike(baseModelQueriable) - - override fun glob(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.glob(baseModelQueriable) - - override fun greaterThan(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.greaterThan(baseModelQueriable) - - override fun greaterThanOrEq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.greaterThanOrEq(baseModelQueriable) - - override fun lessThan(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.lessThan(baseModelQueriable) - - override fun lessThanOrEq(baseModelQueriable: BaseModelQueriable<*>): Operator<*> = - operator.lessThanOrEq(baseModelQueriable) - - override fun between(baseModelQueriable: BaseModelQueriable<*>): Operator.Between<*> = - operator.between(baseModelQueriable) - - override fun `in`(firstBaseModelQueriable: BaseModelQueriable<*>, - vararg baseModelQueriables: BaseModelQueriable<*>): Operator.In<*> = - operator.`in`(firstBaseModelQueriable, *baseModelQueriables) - - override fun notIn(firstBaseModelQueriable: BaseModelQueriable<*>, - vararg baseModelQueriables: BaseModelQueriable<*>): Operator.In<*> = - operator.notIn(firstBaseModelQueriable, *baseModelQueriables) - - override fun concatenate(conditional: IConditional): Operator<*> = - operator.concatenate(conditional) - - override fun plus(value: BaseModelQueriable<*>): Operator<*> = operator.plus(value) - - override fun minus(value: BaseModelQueriable<*>): Operator<*> = operator.minus(value) - - override fun div(value: BaseModelQueriable<*>): Operator<*> = operator.div(value) - - override fun times(value: BaseModelQueriable<*>): Operator<*> = operator.times(value) - - override fun rem(value: BaseModelQueriable<*>): Operator<*> = operator.rem(value) - - override fun plus(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.PLUS, - nameAlias.fullName(), property.toString())) - } - - override fun minus(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.MINUS, - nameAlias.fullName(), property.toString())) - } - - override fun div(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.DIVISION, - nameAlias.fullName(), property.toString())) - } - - override fun times(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.MULTIPLY, - nameAlias.fullName(), property.toString())) - } - - override fun rem(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.MOD, - nameAlias.fullName(), property.toString())) - } - - override fun concatenate(property: IProperty<*>): Property { - return Property(table, NameAlias.joinNames(Operator.Operation.CONCATENATE, - nameAlias.fullName(), property.toString())) - } - - override fun `as`(aliasName: String): Property { - return Property(table, nameAlias - .newBuilder() - .`as`(aliasName) - .build()) - } - - override fun distinct(): Property = Property(table, distinctAliasName) - - override fun withTable(tableNameAlias: NameAlias): Property { - return Property(table, nameAlias - .newBuilder() - .withTable(tableNameAlias.tableName) - .build()) - } - - override fun `is`(value: T?): Operator = operator.`is`(value) - - override fun eq(value: T?): Operator = operator.eq(value) - - override fun isNot(value: T?): Operator = operator.isNot(value) - - override fun notEq(value: T?): Operator = operator.notEq(value) - - override fun greaterThan(value: T): Operator = operator.greaterThan(value) - - override fun greaterThanOrEq(value: T): Operator = operator.greaterThanOrEq(value) - - override fun lessThan(value: T): Operator = operator.lessThan(value) - - override fun lessThanOrEq(value: T): Operator = operator.lessThanOrEq(value) - - override fun between(value: T): Operator.Between = operator.between(value) - - override fun `in`(firstValue: T, vararg values: T): Operator.In = - operator.`in`(firstValue, *values) - - override fun notIn(firstValue: T, vararg values: T): Operator.In = - operator.notIn(firstValue, *values) - - override fun `in`(values: Collection): Operator.In = operator.`in`(values) - - override fun notIn(values: Collection): Operator.In = operator.notIn(values) - - override fun concatenate(value: Any?): Operator = operator.concatenate(value) - - override fun plus(value: T): Operator = operator.plus(value) - - override fun minus(value: T): Operator = operator.minus(value) - - override fun div(value: T): Operator = operator.div(value) - - override fun times(value: T): Operator = operator.times(value) - - override fun rem(value: T): Operator = operator.rem(value) - - override fun asc(): OrderBy = OrderBy.fromProperty(this).ascending() - - override fun desc(): OrderBy = OrderBy.fromProperty(this).descending() - - companion object { - - @JvmStatic - val ALL_PROPERTY = Property(null, NameAlias.rawBuilder("*").build()) - - @JvmStatic - val NO_PROPERTY = Property(null, NameAlias.rawBuilder("").build()) - - @JvmStatic - val WILDCARD: Property<*> = Property(null, NameAlias.rawBuilder("?").build()) - - @JvmStatic - fun allProperty(table: Class<*>): Property = - Property(table, NameAlias.rawBuilder("*").build()).withTable() - } -} - - diff --git a/lib/src/main/kotlin/com/dbflow5/query/property/TypeConvertedProperty.kt b/lib/src/main/kotlin/com/dbflow5/query/property/TypeConvertedProperty.kt deleted file mode 100644 index cec85b6597791b26c5a9d21c86b88fe0a74cfc76..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/query/property/TypeConvertedProperty.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.dbflow5.query.property - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.config.FlowManager -import com.dbflow5.converter.TypeConverter -import com.dbflow5.query.NameAlias -import com.dbflow5.query.Operator -import com.dbflow5.query.nameAlias - -/** - * Description: Provides convenience methods for [TypeConverter] when constructing queries. - * - * @author Andrew Grosner (fuzz) - */ - -class TypeConvertedProperty : Property { - - private var databaseProperty: TypeConvertedProperty? = null - - private val convertToDB: Boolean - private val getter: TypeConverterGetter - - override val table: Class<*> - get() = super.table!! - - /** - * Generated by the compiler, looks up the type converter based on [ModelAdapter] when needed. - * This is so we can properly retrieve the type converter at any time. - */ - interface TypeConverterGetter { - - fun getTypeConverter(modelClass: Class<*>): TypeConverter<*, *> - } - - override val operator: Operator - get() = Operator.op(nameAlias, table, getter, convertToDB) - - constructor(table: Class<*>, nameAlias: NameAlias, - convertToDB: Boolean, - getter: TypeConverterGetter) : super(table, nameAlias) { - this.convertToDB = convertToDB - this.getter = getter - } - - constructor(table: Class<*>, columnName: String, - convertToDB: Boolean, - getter: TypeConverterGetter) : super(table, columnName.nameAlias) { - this.convertToDB = convertToDB - this.getter = getter - } - - override fun withTable(): TypeConvertedProperty { - val nameAlias = this.nameAlias - .newBuilder() - .withTable(FlowManager.getTableName(table)) - .build() - return TypeConvertedProperty(this.table, nameAlias, this.convertToDB, this.getter) - } - - /** - * @return A new [Property] that corresponds to the inverted type of the [TypeConvertedProperty]. - * Provides a convenience for supplying type converted methods within the DataClass of the [TypeConverter] - */ - fun invertProperty(): Property = databaseProperty - ?: TypeConvertedProperty(table, nameAlias, - !convertToDB, object : TypeConverterGetter { - override fun getTypeConverter(modelClass: Class<*>): TypeConverter<*, *> = - getter.getTypeConverter(modelClass) - }).also { databaseProperty = it } - - override fun withTable(tableNameAlias: NameAlias): TypeConvertedProperty { - val nameAlias = this.nameAlias - .newBuilder() - .withTable(tableNameAlias.tableName) - .build() - return TypeConvertedProperty(this.table, nameAlias, this.convertToDB, this.getter) - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/ContentResolverNotifier.kt b/lib/src/main/kotlin/com/dbflow5/runtime/ContentResolverNotifier.kt deleted file mode 100644 index 9d7babcb1720c48c0e4f72d6a7b2a430cd3fde4c..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/ContentResolverNotifier.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.dbflow5.runtime - -import android.content.ContentResolver -import android.content.Context -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.getNotificationUri -import com.dbflow5.query.SQLOperator -import com.dbflow5.structure.ChangeAction - -/** - * The default use case, it notifies via the [ContentResolver] system. - * - * @param [authority] Specify the content URI authority you wish to use here. This will get propagated - * everywhere that changes get called from in a specific database. - */ -class ContentResolverNotifier(private val context: Context, - val authority: String) : ModelNotifier { - - override fun notifyModelChanged(model: T, adapter: ModelAdapter, - action: ChangeAction) { - if (FlowContentObserver.shouldNotify()) { - context.contentResolver.notifyChange( - getNotificationUri(authority, adapter.table, action, - adapter.getPrimaryConditionClause(model).conditions), null, true) - } - } - - override fun notifyTableChanged(table: Class, action: ChangeAction) { - if (FlowContentObserver.shouldNotify()) { - context.contentResolver.notifyChange( - getNotificationUri(authority, table, action, null as Array?), null, true) - } - } - - override fun newRegister(): TableNotifierRegister = FlowContentTableNotifierRegister(context, authority) - - class FlowContentTableNotifierRegister(private val context: Context, contentAuthority: String) : TableNotifierRegister { - - private val flowContentObserver = FlowContentObserver(contentAuthority) - - private var tableChangedListener: OnTableChangedListener? = null - - private val internalContentChangeListener = object : OnTableChangedListener { - override fun onTableChanged(table: Class<*>?, action: ChangeAction) { - tableChangedListener?.onTableChanged(table, action) - } - } - - init { - flowContentObserver.addOnTableChangedListener(internalContentChangeListener) - } - - override fun register(tClass: Class) { - flowContentObserver.registerForContentChanges(context, tClass) - } - - override fun unregister(tClass: Class) { - flowContentObserver.unregisterForContentChanges(context) - } - - override fun unregisterAll() { - flowContentObserver.removeTableChangedListener(internalContentChangeListener) - this.tableChangedListener = null - } - - override fun setListener(listener: OnTableChangedListener?) { - this.tableChangedListener = listener - } - - override val isSubscribed: Boolean - get() = !flowContentObserver.isSubscribed - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/DirectModelNotifier.kt b/lib/src/main/kotlin/com/dbflow5/runtime/DirectModelNotifier.kt deleted file mode 100644 index 409be0e1148d9758aa1c738491c3581d81bea630..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/DirectModelNotifier.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.dbflow5.runtime - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.config.DatabaseConfig -import com.dbflow5.structure.ChangeAction - -/** - * Description: Directly notifies about model changes. Users should use [.get] to use the shared - * instance in [DatabaseConfig.Builder] - */ -class DirectModelNotifier -/** - * Private constructor. Use shared [.get] to ensure singular instance. - */ -private constructor() : ModelNotifier { - - private val modelChangedListenerMap = linkedMapOf, MutableSet>>() - - private val tableChangedListenerMap = linkedMapOf, MutableSet>() - - interface OnModelStateChangedListener { - fun onModelChanged(model: T, action: ChangeAction) - } - - interface ModelChangedListener : OnModelStateChangedListener, OnTableChangedListener - - init { - if (instanceCount > 0) { - throw IllegalStateException("Cannot instantiate more than one DirectNotifier. Use DirectNotifier.get()") - } - instanceCount++ - } - - @Suppress("UNCHECKED_CAST") - override fun notifyModelChanged(model: T, adapter: ModelAdapter, - action: ChangeAction) { - modelChangedListenerMap[adapter.table] - ?.forEach { listener -> - (listener as OnModelStateChangedListener).onModelChanged(model, action) - } - tableChangedListenerMap[adapter.table] - ?.forEach { listener -> listener.onTableChanged(adapter.table, action) } - } - - override fun notifyTableChanged(table: Class, action: ChangeAction) { - tableChangedListenerMap[table]?.forEach { listener -> listener.onTableChanged(table, action) } - } - - override fun newRegister(): TableNotifierRegister = DirectTableNotifierRegister(this) - - fun registerForModelChanges(table: Class, - listener: ModelChangedListener) { - registerForModelStateChanges(table, listener) - registerForTableChanges(table, listener) - } - - fun registerForModelStateChanges(table: Class, - listener: OnModelStateChangedListener) { - var listeners = modelChangedListenerMap[table] - if (listeners == null) { - listeners = linkedSetOf() - modelChangedListenerMap.put(table, listeners) - } - listeners.add(listener) - } - - fun registerForTableChanges(table: Class, - listener: OnTableChangedListener) { - var listeners = tableChangedListenerMap[table] - if (listeners == null) { - listeners = linkedSetOf() - tableChangedListenerMap.put(table, listeners) - } - listeners.add(listener) - } - - fun unregisterForModelChanges(table: Class, - listener: ModelChangedListener) { - unregisterForModelStateChanges(table, listener) - unregisterForTableChanges(table, listener) - } - - - fun unregisterForModelStateChanges(table: Class, - listener: OnModelStateChangedListener) { - val listeners = modelChangedListenerMap[table] - listeners?.remove(listener) - } - - fun unregisterForTableChanges(table: Class, - listener: OnTableChangedListener) { - val listeners = tableChangedListenerMap[table] - listeners?.remove(listener) - } - - /** - * Clears all listeners. - */ - fun clearListeners() = tableChangedListenerMap.clear() - - private class DirectTableNotifierRegister(private val directModelNotifier: DirectModelNotifier) - : TableNotifierRegister { - private val registeredTables = arrayListOf>() - - private var modelChangedListener: OnTableChangedListener? = null - - private val internalChangeListener = object : OnTableChangedListener { - override fun onTableChanged(table: Class<*>?, action: ChangeAction) { - modelChangedListener?.onTableChanged(table, action) - } - } - - override fun register(tClass: Class) { - registeredTables.add(tClass) - directModelNotifier.registerForTableChanges(tClass, internalChangeListener) - } - - override fun unregister(tClass: Class) { - registeredTables.remove(tClass) - directModelNotifier.unregisterForTableChanges(tClass, internalChangeListener) - } - - override fun unregisterAll() { - registeredTables.forEach { table -> - directModelNotifier.unregisterForTableChanges(table, internalChangeListener) - } - this.modelChangedListener = null - } - - override fun setListener(listener: OnTableChangedListener?) { - this.modelChangedListener = listener - } - - override val isSubscribed: Boolean - get() = !registeredTables.isEmpty() - } - - companion object { - - internal var instanceCount = 0 - - private val notifier: DirectModelNotifier by lazy { - DirectModelNotifier() - } - - @JvmStatic - fun get(): DirectModelNotifier = notifier - - operator fun invoke() = get() - } - -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/FlowContentObserver.kt b/lib/src/main/kotlin/com/dbflow5/runtime/FlowContentObserver.kt deleted file mode 100644 index 99efd09b4f8edfc4959ab635d645f8660edc59ea..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/FlowContentObserver.kt +++ /dev/null @@ -1,287 +0,0 @@ -package com.dbflow5.runtime - -import android.annotation.TargetApi -import android.content.ContentResolver -import android.content.Context -import android.database.ContentObserver -import android.net.Uri -import android.os.Build -import android.os.Build.VERSION_CODES -import android.os.Handler -import com.dbflow5.TABLE_QUERY_PARAM -import com.dbflow5.config.DatabaseConfig -import com.dbflow5.config.FlowLog -import com.dbflow5.config.FlowManager -import com.dbflow5.getNotificationUri -import com.dbflow5.query.NameAlias -import com.dbflow5.query.Operator -import com.dbflow5.query.SQLOperator -import com.dbflow5.structure.ChangeAction -import com.dbflow5.structure.Model -import java.util.concurrent.CopyOnWriteArraySet -import java.util.concurrent.atomic.AtomicInteger - -/** - * Description: Listens for [Model] changes. Register for specific - * tables with [.addModelChangeListener]. - * Provides ability to register and deregister listeners for when data is inserted, deleted, updated, and saved if the device is - * above [VERSION_CODES.JELLY_BEAN]. If below it will only provide one callback. This is to be paired - * with the [ContentResolverNotifier] specified in the [DatabaseConfig]. - * - * @param [contentAuthority] Reuse the same authority as defined in your manifest and [ContentResolverNotifier]. - */ -open class FlowContentObserver(private val contentAuthority: String, - handler: Handler? = null) : ContentObserver(handler) { - - private val modelChangeListeners = CopyOnWriteArraySet() - private val onTableChangedListeners = CopyOnWriteArraySet() - private val registeredTables = hashMapOf>() - private val notificationUris = hashSetOf() - private val tableUris = hashSetOf() - - protected var isInTransaction = false - private var notifyAllUris = false - - val isSubscribed: Boolean - get() = !registeredTables.isEmpty() - - /** - * Listens for specific model changes. This is only available in [VERSION_CODES.JELLY_BEAN] - * or higher due to the api of [ContentObserver]. - */ - interface OnModelStateChangedListener { - - /** - * Notifies that the state of a [Model] - * has changed for the table this is registered for. - * - * @param table The table that this change occurred on. This is ONLY available on [VERSION_CODES.JELLY_BEAN] - * and up. - * @param action The action on the model. for versions prior to [VERSION_CODES.JELLY_BEAN] , - * the [Action.CHANGE] will always be called for any action. - * @param primaryKeyValues The array of primary [SQLOperator] of what changed. Call [SQLOperator.columnName] - * and [SQLOperator.value] to get each information. - */ - fun onModelStateChanged(table: Class<*>?, action: ChangeAction, primaryKeyValues: Array) - } - - interface ContentChangeListener : OnModelStateChangedListener, OnTableChangedListener - - /** - * If true, this class will get specific when it needs to, such as using all [Action] qualifiers. - * If false, it only uses the [Action.CHANGE] action in callbacks. - * - * @param notifyAllUris - */ - fun setNotifyAllUris(notifyAllUris: Boolean) { - this.notifyAllUris = notifyAllUris - } - - /** - * Starts a transaction where when it is finished, this class will receive a notification of all of the changes by - * calling [.endTransactionAndNotify]. Note it may lead to unexpected behavior if called from different threads. - */ - fun beginTransaction() { - if (!isInTransaction) { - isInTransaction = true - } - } - - /** - * Ends the transaction where it finishes, and will call [.onChange] for Jelly Bean and up for - * every URI called (if set), or [.onChange] once for lower than Jelly bean. - */ - open fun endTransactionAndNotify() { - if (isInTransaction) { - isInTransaction = false - - if (Build.VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) { - onChange(true) - } else { - synchronized(notificationUris) { - for (uri in notificationUris) { - onChange(uri, true) - } - notificationUris.clear() - } - synchronized(tableUris) { - for (uri in tableUris) { - for (onTableChangedListener in onTableChangedListeners) { - uri.authority?.let { authority -> - uri.fragment?.let { fragment -> - onTableChangedListener.onTableChanged(registeredTables[authority], - ChangeAction.valueOf(fragment)) - } - } - } - } - tableUris.clear() - } - } - } - } - - /** - * Add a listener for model changes - * - * @param modelChangeListener Generic model change events from an [Action] - */ - fun addModelChangeListener(modelChangeListener: OnModelStateChangedListener) { - modelChangeListeners.add(modelChangeListener) - } - - /** - * Removes a listener for model changes - * - * @param modelChangeListener Generic model change events from a [Action] - */ - fun removeModelChangeListener(modelChangeListener: OnModelStateChangedListener) { - modelChangeListeners.remove(modelChangeListener) - } - - fun addOnTableChangedListener(onTableChangedListener: OnTableChangedListener) { - onTableChangedListeners.add(onTableChangedListener) - } - - fun removeTableChangedListener(onTableChangedListener: OnTableChangedListener) { - onTableChangedListeners.remove(onTableChangedListener) - } - - /** - * Add a listener for model + table changes - * - * @param contentChangeListener Generic model change events from an [Action] - */ - fun addContentChangeListener(contentChangeListener: ContentChangeListener) { - modelChangeListeners.add(contentChangeListener) - onTableChangedListeners.add(contentChangeListener) - } - - /** - * Removes a listener for model + table changes - * - * @param contentChangeListener Generic model change events from a [Action] - */ - fun removeContentChangeListener(contentChangeListener: ContentChangeListener) { - modelChangeListeners.remove(contentChangeListener) - onTableChangedListeners.remove(contentChangeListener) - } - - /** - * Registers the observer for model change events for specific class. - */ - open fun registerForContentChanges(context: Context, - table: Class<*>) { - registerForContentChanges(context.contentResolver, table) - } - - /** - * Registers the observer for model change events for specific class. - */ - fun registerForContentChanges(contentResolver: ContentResolver, - table: Class<*>) { - contentResolver.registerContentObserver( - getNotificationUri(contentAuthority, table, null), true, this) - REGISTERED_COUNT.incrementAndGet() - if (!registeredTables.containsValue(table)) { - registeredTables.put(FlowManager.getTableName(table), table) - } - } - - /** - * Unregisters this list for model change events - */ - fun unregisterForContentChanges(context: Context) { - context.contentResolver.unregisterContentObserver(this) - REGISTERED_COUNT.decrementAndGet() - registeredTables.clear() - } - - override fun onChange(selfChange: Boolean) { - modelChangeListeners.forEach { it.onModelStateChanged(null, ChangeAction.CHANGE, arrayOf()) } - onTableChangedListeners.forEach { it.onTableChanged(null, ChangeAction.CHANGE) } - } - - @TargetApi(VERSION_CODES.JELLY_BEAN) - override fun onChange(selfChange: Boolean, uri: Uri) { - onChange(uri, false) - } - - @TargetApi(VERSION_CODES.JELLY_BEAN) - private fun onChange(uri: Uri, calledInternally: Boolean) { - var locUri = uri - val fragment = locUri.fragment - val tableName = locUri.getQueryParameter(TABLE_QUERY_PARAM) - - val queryNames = locUri.queryParameterNames - val columnsChanged = arrayListOf() - queryNames.asSequence() - .filter { it != TABLE_QUERY_PARAM } - .forEach { key -> - val param = Uri.decode(locUri.getQueryParameter(key)) - val columnName = Uri.decode(key) - columnsChanged += Operator.op(NameAlias.Builder(columnName).build()).eq(param) - } - - val table = tableName?.let { t -> registeredTables[t] } - if (table != null && fragment != null) { - var action = ChangeAction.valueOf(fragment) - if (!isInTransaction) { - modelChangeListeners.forEach { - it.onModelStateChanged(table, action, columnsChanged.toTypedArray()) - } - - if (!calledInternally) { - onTableChangedListeners.forEach { it.onTableChanged(table, action) } - } - } else { - // convert this uri to a CHANGE op if we don't care about individual changes. - if (!notifyAllUris) { - action = ChangeAction.CHANGE - locUri = getNotificationUri(contentAuthority, table, action) - } - synchronized(notificationUris) { - // add and keep track of unique notification uris for when transaction completes. - notificationUris.add(locUri) - } - - synchronized(tableUris) { - tableUris.add(getNotificationUri(contentAuthority, table, action)) - } - } - } else { - FlowLog.log(FlowLog.Level.W, "Received URI change for unregistered table $tableName . URI ignored.") - } - } - - companion object { - - private val REGISTERED_COUNT = AtomicInteger(0) - private var forceNotify = false - - /** - * @return true if we have registered for content changes. Otherwise we do not notify - * in [SqlUtils] - * for efficiency purposes. - */ - fun shouldNotify(): Boolean { - return forceNotify || REGISTERED_COUNT.get() > 0 - } - - /** - * Removes count of observers registered, so we do not send out calls when [Model] changes. - */ - fun clearRegisteredObserverCount() { - REGISTERED_COUNT.set(0) - } - - /** - * @param forceNotify if true, this will force itself to notify whenever a model changes even though - * an observer (appears to be) is not registered. - */ - fun setShouldForceNotify(forceNotify: Boolean) { - Companion.forceNotify = forceNotify - } - } - -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/ModelNotifier.kt b/lib/src/main/kotlin/com/dbflow5/runtime/ModelNotifier.kt deleted file mode 100644 index e06a8e9e72b07b95aa335a7f1997d7ff95f644d8..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/ModelNotifier.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.dbflow5.runtime - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.structure.ChangeAction - -/** - * Interface for defining how we notify model changes. - */ -interface ModelNotifier { - - fun notifyModelChanged(model: T, adapter: ModelAdapter, action: ChangeAction) - - fun notifyTableChanged(table: Class, action: ChangeAction) - - fun newRegister(): TableNotifierRegister -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/NotifyDistributor.kt b/lib/src/main/kotlin/com/dbflow5/runtime/NotifyDistributor.kt deleted file mode 100644 index 0f9069c46a99cbb5a89620158870d50651053add..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/NotifyDistributor.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.dbflow5.runtime - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.config.FlowManager -import com.dbflow5.structure.ChangeAction - -/** - * Description: Distributes notifications to the [ModelNotifier]. - */ -class NotifyDistributor : ModelNotifier { - - override fun newRegister(): TableNotifierRegister { - throw RuntimeException("Cannot create a register from the distributor class") - } - - override fun notifyModelChanged(model: T, - adapter: ModelAdapter, - action: ChangeAction) { - FlowManager.getModelNotifierForTable(adapter.table) - .notifyModelChanged(model, adapter, action) - } - - /** - * Notifies listeners of table-level changes from the SQLite-wrapper language. - */ - override fun notifyTableChanged(table: Class, - action: ChangeAction) { - FlowManager.getModelNotifierForTable(table).notifyTableChanged(table, action) - } - - companion object { - - private val distributor by lazy { NotifyDistributor() } - - @JvmStatic - fun get(): NotifyDistributor = distributor - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/runtime/TableNotifierRegister.kt b/lib/src/main/kotlin/com/dbflow5/runtime/TableNotifierRegister.kt deleted file mode 100644 index 02b5fbaf761c215c09b093d95e09f0ffe1261285..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/runtime/TableNotifierRegister.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.dbflow5.runtime - -import com.dbflow5.structure.ChangeAction - -/** - * Description: Defines how [ModelNotifier] registers listeners. Abstracts that away. - */ -interface TableNotifierRegister { - - val isSubscribed: Boolean - - fun register(tClass: Class) - - fun unregister(tClass: Class) - - fun unregisterAll() - - fun setListener(listener: OnTableChangedListener?) - - -} - -inline fun TableNotifierRegister.setListener(crossinline listener: (Class<*>?, ChangeAction) -> Unit) = - setListener(object : OnTableChangedListener { - override fun onTableChanged(table: Class<*>?, action: ChangeAction) = listener(table, action) - }) - diff --git a/lib/src/main/kotlin/com/dbflow5/structure/BaseModel.kt b/lib/src/main/kotlin/com/dbflow5/structure/BaseModel.kt deleted file mode 100644 index 1ab35a678ed8598e3cbb1cd3b2cad5f5cb4ea21b..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/BaseModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.dbflow5.structure - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.annotation.ColumnIgnore -import com.dbflow5.config.FlowManager -import com.dbflow5.config.modelAdapter -import com.dbflow5.database.DatabaseWrapper - -/** - * Description: The base implementation of [Model]. It is recommended to use this class as - * the base for your [Model], but it is not required. - */ -open class BaseModel : Model { - - /** - * @return The associated [ModelAdapter]. The [FlowManager] - * may throw a [InvalidDBConfiguration] for this call if this class - * is not associated with a table, so be careful when using this method. - */ - @delegate:ColumnIgnore - @delegate:Transient - val modelAdapter: ModelAdapter by lazy { javaClass.modelAdapter } - - @Suppress("UNCHECKED_CAST") - override fun load(wrapper: DatabaseWrapper): T? = modelAdapter.load(this, wrapper) as T? - - override fun save(wrapper: DatabaseWrapper): Boolean = modelAdapter.save(this, wrapper) - - override fun delete(wrapper: DatabaseWrapper): Boolean = modelAdapter.delete(this, wrapper) - - override fun update(wrapper: DatabaseWrapper): Boolean = modelAdapter.update(this, wrapper) - - override fun insert(wrapper: DatabaseWrapper): Long = modelAdapter.insert(this, wrapper) - - override fun exists(wrapper: DatabaseWrapper): Boolean = modelAdapter.exists(this, wrapper) -} diff --git a/lib/src/main/kotlin/com/dbflow5/structure/BaseQueryModel.kt b/lib/src/main/kotlin/com/dbflow5/structure/BaseQueryModel.kt deleted file mode 100644 index 7993a2f13010a28cdfe7e4c6bea2248bf6b86947..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/BaseQueryModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.dbflow5.structure - -import com.dbflow5.annotation.QueryModel -import com.dbflow5.database.DatabaseWrapper - -/** - * Description: Provides a base class for objects that represent [QueryModel]. - */ -class BaseQueryModel : NoModificationModel() { - - override fun exists(wrapper: DatabaseWrapper): Boolean { - throw InvalidSqlViewOperationException("Query ${wrapper.javaClass.name} does not exist as a table." + - "It's a convenient representation of a complex SQLite cursor.") - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/structure/InvalidDBConfiguration.kt b/lib/src/main/kotlin/com/dbflow5/structure/InvalidDBConfiguration.kt deleted file mode 100644 index 6f4d303562186666ecb053cc410a9731db596840..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/InvalidDBConfiguration.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.dbflow5.structure - -/** - * Description: Thrown when a DB is incorrectly configured. - */ -class InvalidDBConfiguration(message: String) : RuntimeException(message) diff --git a/lib/src/main/kotlin/com/dbflow5/structure/Model.kt b/lib/src/main/kotlin/com/dbflow5/structure/Model.kt deleted file mode 100644 index b5fafe57e5bdb434817fe44d259e2699bd42a9bd..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/Model.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.dbflow5.structure - -import com.dbflow5.config.modelAdapter -import com.dbflow5.database.DatabaseWrapper - -interface Model : ReadOnlyModel { - - /** - * Saves the object in the DB. - * - * @return true if successful - */ - fun save(wrapper: DatabaseWrapper): Boolean - - /** - * Deletes the object in the DB - * - * @return true if successful - */ - fun delete(wrapper: DatabaseWrapper): Boolean - - /** - * Updates an object in the DB. Does not insert on failure. - * - * @return true if successful - */ - fun update(wrapper: DatabaseWrapper): Boolean - - /** - * Inserts the object into the DB - * - * @return the count of the rows affected, should only be 1 here, or -1 if failed. - */ - fun insert(wrapper: DatabaseWrapper): Long - - companion object { - - /** - * Returned when [.insert] occurs in an async state or some kind of issue occurs. - */ - const val INVALID_ROW_ID: Long = -1 - } - -} - -inline fun T.save(databaseWrapper: DatabaseWrapper): Boolean = - modelAdapter().save(this, databaseWrapper) - -inline fun T.insert(databaseWrapper: DatabaseWrapper): Long = - modelAdapter().insert(this, databaseWrapper) - -inline fun T.update(databaseWrapper: DatabaseWrapper): Boolean = - modelAdapter().update(this, databaseWrapper) - -inline fun T.delete(databaseWrapper: DatabaseWrapper): Boolean = - modelAdapter().delete(this, databaseWrapper) - -inline fun T.exists(databaseWrapper: DatabaseWrapper): Boolean = - modelAdapter().exists(this, databaseWrapper) - -inline fun T.load(databaseWrapper: DatabaseWrapper) = - modelAdapter().load(this, databaseWrapper) - diff --git a/lib/src/main/kotlin/com/dbflow5/structure/NoModificationModel.kt b/lib/src/main/kotlin/com/dbflow5/structure/NoModificationModel.kt deleted file mode 100644 index b0dcdc4d91b4e215c0508a625968a0d33a9028b3..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/NoModificationModel.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.dbflow5.structure - -import com.dbflow5.adapter.RetrievalAdapter -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper - -/** - * Description: A convenience class for [ReadOnlyModel]. - */ -abstract class NoModificationModel : ReadOnlyModel { - - @delegate:Transient - private val retrievalAdapter: RetrievalAdapter by lazy { FlowManager.getRetrievalAdapter(javaClass) } - - override fun exists(wrapper: DatabaseWrapper): Boolean = retrievalAdapter.exists(this, wrapper) - - @Suppress("UNCHECKED_CAST") - override fun load(wrapper: DatabaseWrapper): T? = retrievalAdapter.load(this, wrapper) as T? - - /** - * Gets thrown when an operation is not valid for the SQL View - */ - internal class InvalidSqlViewOperationException(detailMessage: String) : RuntimeException(detailMessage) -} diff --git a/lib/src/main/kotlin/com/dbflow5/structure/OneToMany.kt b/lib/src/main/kotlin/com/dbflow5/structure/OneToMany.kt deleted file mode 100644 index 9d801f8a524c1f1b49322f64c014c05c3267d0b1..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/structure/OneToMany.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.dbflow5.structure - -import com.dbflow5.config.FlowManager -import com.dbflow5.query.ModelQueriable -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/** - * Description: - */ -fun oneToMany(query: () -> ModelQueriable) = OneToMany(query) - -/** - * Description: Wraps a [OneToMany] annotation getter into a concise property setter. - */ -class OneToMany(private val query: () -> ModelQueriable) : ReadWriteProperty?> { - - private var list: List? = null - - override fun getValue(thisRef: Any, property: KProperty<*>): List? { - if (list?.isEmpty() != false) { - list = query().queryList(FlowManager.getDatabaseForTable(thisRef::class.java)) - } - return list - } - - override fun setValue(thisRef: Any, property: KProperty<*>, value: List?) { - list = value - } -} \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/BaseTransactionManager.kt b/lib/src/main/kotlin/com/dbflow5/transaction/BaseTransactionManager.kt deleted file mode 100644 index d8549586067fd831669db17657e3a19ddb039fb2..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/BaseTransactionManager.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.dbflow5.transaction - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowLog -import com.dbflow5.runtime.DBBatchSaveQueue - -/** - * Description: The base implementation of Transaction manager. - */ -abstract class BaseTransactionManager(val queue: ITransactionQueue, - databaseDefinition: DBFlowDatabase) { - - private val _saveQueue: DBBatchSaveQueue = DBBatchSaveQueue(databaseDefinition) - - init { - checkQueue() - } - - val saveQueue: DBBatchSaveQueue - get() { - try { - if (!_saveQueue.isAlive) { - _saveQueue.start() - } - } catch (i: IllegalThreadStateException) { - FlowLog.logError(i) // if queue is alive, will throw error. might occur in multithreading. - } - - return _saveQueue - } - - /** - * Checks if queue is running. If not, should be started here. - */ - fun checkQueue() = queue.startIfNotAlive() - - /** - * Stops the queue this manager contains. - */ - fun stopQueue() = queue.quit() - - /** - * Adds a transaction to the [ITransactionQueue]. - * - * @param transaction The transaction to add. - */ - fun addTransaction(transaction: Transaction) = queue.add(transaction) - - /** - * Cancels a transaction on the [ITransactionQueue]. - * - * @param transaction - */ - fun cancelTransaction(transaction: Transaction) = queue.cancel(transaction) -} diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/DefaultTransactionManager.kt b/lib/src/main/kotlin/com/dbflow5/transaction/DefaultTransactionManager.kt deleted file mode 100644 index f0be1dfe468fc6e42d8b457bc26f33b727aebb6a..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/DefaultTransactionManager.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.dbflow5.transaction - -import com.dbflow5.config.DBFlowDatabase - -/** - * Description: This class manages batch database interactions. Places DB operations onto the same Thread. - */ -class DefaultTransactionManager : BaseTransactionManager { - - constructor(databaseDefinition: DBFlowDatabase) - : super(DefaultTransactionQueue("DBFlow Transaction Queue"), databaseDefinition) - - constructor(transactionQueue: ITransactionQueue, - databaseDefinition: DBFlowDatabase) - : super(transactionQueue, databaseDefinition) - -} diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/FastStoreModelTransaction.kt b/lib/src/main/kotlin/com/dbflow5/transaction/FastStoreModelTransaction.kt deleted file mode 100644 index 0e5d1c2cfd39f03625154f2b6a21a3d320bc7375..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/FastStoreModelTransaction.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.dbflow5.transaction - -import com.dbflow5.adapter.InternalAdapter -import com.dbflow5.config.modelAdapter -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.structure.Model - -/** - * Description: Simple interface for acting on a model in a Transaction or list of [Model] - */ -private typealias ProcessModelList = (List, InternalAdapter, DatabaseWrapper) -> Long - -/** - * Description: Similiar to [ProcessModelTransaction] in that it allows you to store a [List] of - * [Model], except that it performs it as efficiently as possible. Also due to way the class operates, - * only one kind of [TModel] is allowed. - */ -class FastStoreModelTransaction internal constructor(builder: Builder) : ITransaction { - - internal val models: List? - internal val processModelList: ProcessModelList - internal val internalAdapter: InternalAdapter - - init { - models = builder.models - processModelList = builder.processModelList - internalAdapter = builder.internalAdapter - } - - override fun execute(databaseWrapper: DatabaseWrapper): Long { - if (models != null) { - return processModelList(models, internalAdapter, databaseWrapper) - } - return 0L - } - - /** - * Makes it easy to build a [ProcessModelTransaction]. - * - * @param - */ - class Builder internal constructor(internal val internalAdapter: InternalAdapter, - internal val processModelList: ProcessModelList) { - internal var models: MutableList = arrayListOf() - - fun add(model: TModel) = apply { - models.add(model) - } - - /** - * Adds all specified models to the [ArrayList]. - */ - @SafeVarargs - fun addAll(vararg models: TModel) = apply { - this.models.addAll(models.toList()) - } - - /** - * Adds a [Collection] of [Model] to the existing [ArrayList]. - */ - fun addAll(models: Collection?) = apply { - if (models != null) { - this.models.addAll(models) - } - } - - /** - * @return A new [ProcessModelTransaction]. Subsequent calls to this method produce - * new instances. - */ - fun build(): FastStoreModelTransaction = FastStoreModelTransaction(this) - } - - companion object { - - @JvmStatic - fun saveBuilder(internalAdapter: InternalAdapter): Builder = - Builder(internalAdapter) { tModels, adapter, wrapper -> adapter.saveAll(tModels, wrapper) } - - @JvmStatic - fun insertBuilder(internalAdapter: InternalAdapter): Builder = - Builder(internalAdapter) { tModels, adapter, wrapper -> adapter.insertAll(tModels, wrapper) } - - - @JvmStatic - fun updateBuilder(internalAdapter: InternalAdapter): Builder = - Builder(internalAdapter) { tModels, adapter, wrapper -> adapter.updateAll(tModels, wrapper) } - - @JvmStatic - fun deleteBuilder(internalAdapter: InternalAdapter): Builder = - Builder(internalAdapter) { tModels, adapter, wrapper -> adapter.deleteAll(tModels, wrapper) } - } -} - -inline fun Collection.fastSave(): FastStoreModelTransaction.Builder = - FastStoreModelTransaction.saveBuilder(modelAdapter()).addAll(this) - -inline fun Collection.fastInsert(): FastStoreModelTransaction.Builder = - FastStoreModelTransaction.insertBuilder(modelAdapter()).addAll(this) - -inline fun Collection.fastUpdate(): FastStoreModelTransaction.Builder = - FastStoreModelTransaction.updateBuilder(modelAdapter()).addAll(this) - -inline fun Collection.fastDelete(): FastStoreModelTransaction.Builder = - FastStoreModelTransaction.deleteBuilder(modelAdapter()).addAll(this) \ No newline at end of file diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionQueue.kt b/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionQueue.kt deleted file mode 100644 index 61e3bd75ad6a37d7f9d6452aa42c26f012c310c3..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionQueue.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.dbflow5.transaction - -import android.os.Looper -import android.os.Process.THREAD_PRIORITY_BACKGROUND -import android.os.Process.setThreadPriority -import com.dbflow5.config.FlowLog -import java.util.concurrent.PriorityBlockingQueue - -/** - * Description: Orders [Transaction] in a priority-based queue, enabling you perform higher priority - * tasks first (such as retrievals) if you are attempting many DB operations at once. - */ -class PriorityTransactionQueue -/** - * Creates a queue with the specified name to ID it. - * - * @param name - */ -(name: String) : Thread(name), ITransactionQueue { - - private val queue = PriorityBlockingQueue>>() - - private var isQuitting = false - - override fun run() { - Looper.prepare() - setThreadPriority(THREAD_PRIORITY_BACKGROUND) - var transaction: PriorityEntry> - while (true) { - try { - transaction = queue.take() - } catch (e: InterruptedException) { - if (isQuitting) { - synchronized(queue) { - queue.clear() - } - return - } - continue - } - - transaction.entry.executeSync() - } - } - - override fun add(transaction: Transaction) { - synchronized(queue) { - val priorityEntry = PriorityEntry(transaction) - if (!queue.contains(priorityEntry)) { - queue.add(priorityEntry) - } - } - } - - /** - * Cancels the specified request. - * - * @param transaction The transaction to cancel (if still in the queue). - */ - override fun cancel(transaction: Transaction) { - synchronized(queue) { - val priorityEntry = PriorityEntry(transaction) - if (queue.contains(priorityEntry)) { - queue.remove(priorityEntry) - } - } - } - - /** - * Cancels all requests by a specific tag - * - * @param name - */ - override fun cancel(name: String) { - synchronized(queue) { - val it = queue.iterator() - while (it.hasNext()) { - val next = it.next().entry - if (next.name != null && next.name == name) { - it.remove() - } - } - } - } - - override fun startIfNotAlive() { - synchronized(this) { - if (!isAlive) { - try { - start() - } catch (i: IllegalThreadStateException) { - // log if failure from thread is still alive. - FlowLog.log(FlowLog.Level.E, throwable = i) - } - - } - } - } - - /** - * Quits this process - */ - override fun quit() { - isQuitting = true - interrupt() - } - - internal data class PriorityEntry>(val entry: E) - : Comparable>> { - private val transactionWrapper: PriorityTransactionWrapper = - when { - entry.transaction is PriorityTransactionWrapper -> entry.transaction - else -> entry.transaction.withPriority() - } - - override fun compareTo(other: PriorityEntry>): Int = - transactionWrapper.compareTo(other.transactionWrapper) - - } -} diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionWrapper.kt b/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionWrapper.kt deleted file mode 100644 index 22bebb684b88f5a834735f115c5a5783e8df1913..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/PriorityTransactionWrapper.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.dbflow5.transaction - -import androidx.annotation.IntDef -import com.dbflow5.database.DatabaseWrapper - -/** - * Constructs a [PriorityTransactionWrapper] from an [ITransaction] specifying the priority. - */ -fun ITransaction<*>.withPriority(@PriorityTransactionWrapper.Priority - priority: Int = PriorityTransactionWrapper.PRIORITY_NORMAL): PriorityTransactionWrapper = - PriorityTransactionWrapper(this, priority) - -/** - * Description: Provides transaction with priority. Meant to be used in a [PriorityTransactionQueue]. - */ -class PriorityTransactionWrapper(private val transaction: ITransaction<*>, - private val priority: Int = PriorityTransactionWrapper.PRIORITY_NORMAL) - : ITransaction, Comparable { - - @Suppress("RemoveEmptyPrimaryConstructor") - @Retention(AnnotationRetention.SOURCE) - @IntDef(PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH, PRIORITY_UI) - annotation class Priority() - - override fun execute(databaseWrapper: DatabaseWrapper) { - transaction.execute(databaseWrapper) - } - - override fun compareTo(other: PriorityTransactionWrapper): Int = other.priority - priority - - companion object { - - /** - * Low priority requests, reserved for non-essential tasks - */ - const val PRIORITY_LOW = 0 - - /** - * The main of the requests, good for when adding a bunch of - * data to the DB that the app does not access right away (default). - */ - const val PRIORITY_NORMAL = 1 - - /** - * Reserved for tasks that will influence user interaction, such as displaying data in the UI - * some point in the future (not necessarily right away) - */ - const val PRIORITY_HIGH = 2 - - /** - * Reserved for only immediate tasks and all forms of fetching that will display on the UI - */ - const val PRIORITY_UI = 5 - } - -} diff --git a/lib/src/main/kotlin/com/dbflow5/transaction/TransactionWrapper.kt b/lib/src/main/kotlin/com/dbflow5/transaction/TransactionWrapper.kt deleted file mode 100644 index b6c43c858812ff626b92662cf111102832ed263c..0000000000000000000000000000000000000000 --- a/lib/src/main/kotlin/com/dbflow5/transaction/TransactionWrapper.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5.transaction - -import com.dbflow5.database.DatabaseWrapper - -/** - * Description: Wraps multiple transactions together. - */ -class TransactionWrapper : ITransaction { - - private val transactions = arrayListOf>() - - constructor(vararg transactions: ITransaction) { - this.transactions.addAll(transactions) - } - - constructor(transactions: Collection>) { - this.transactions.addAll(transactions) - } - - override fun execute(databaseWrapper: DatabaseWrapper) { - transactions.forEach { it.execute(databaseWrapper) } - } -} diff --git a/lib/src/main/resources/base/element/string.json b/lib/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1176aeed47b08b7dae6c1f0934ccc9f18e7708be --- /dev/null +++ b/lib/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "lib" + } + ] +} diff --git a/lib/src/test/java/com/dbflow5/adapter/ExampleTest.java b/lib/src/test/java/com/dbflow5/adapter/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7ec132a3e06815603890d06cdd6fe2d0b792681a --- /dev/null +++ b/lib/src/test/java/com/dbflow5/adapter/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.adapter; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/livedata/build.gradle b/livedata/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ef62e9ed946eb6c6a7e799f989ec1a963ba67afb --- /dev/null +++ b/livedata/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + implementation(project(":lib")) +} diff --git a/livedata/build.gradle.kts b/livedata/build.gradle.kts deleted file mode 100644 index f7e9480569c61cad0ff9127b1ae92c21c4c16bcc..0000000000000000000000000000000000000000 --- a/livedata/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.ArchMin) - targetSdkVersion(Versions.TargetSdk) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - implementation(project(":lib")) - api(Dependencies.AndroidX.LiveData) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/livedata/consumer-rules.pro b/livedata/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/livedata/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/livedata/gradle.properties b/livedata/gradle.properties deleted file mode 100644 index 1b622336c25b603c5ccbc2af1242fe60cd87d68b..0000000000000000000000000000000000000000 --- a/livedata/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-livedata -bt_packaging=aar -bt_artifact_id=dbflow-livedata \ No newline at end of file diff --git a/livedata/proguard-rules.pro b/livedata/proguard-rules.pro index f1b424510da51fd82143bc74a0a801ae5a1e2fcd..f7666e47561d514b2a76d5a7dfbb43ede86da92a 100644 --- a/livedata/proguard-rules.pro +++ b/livedata/proguard-rules.pro @@ -1,21 +1 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/livedata/src/main/AndroidManifest.xml b/livedata/src/main/AndroidManifest.xml deleted file mode 100644 index 7f0cf668e8c7724794e07afa4c86873eafec3dde..0000000000000000000000000000000000000000 --- a/livedata/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/livedata/src/main/config.json b/livedata/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..f11aebb61737871ffef0b87059658d0834e6a461 --- /dev/null +++ b/livedata/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.livedata", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "livedata", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/livedata/src/main/java/com/dbflow5/livedata/ILiveData.java b/livedata/src/main/java/com/dbflow5/livedata/ILiveData.java new file mode 100644 index 0000000000000000000000000000000000000000..60f13547ab0dd43d5f43bb08c5ab5f89a81a30fa --- /dev/null +++ b/livedata/src/main/java/com/dbflow5/livedata/ILiveData.java @@ -0,0 +1,7 @@ +package com.dbflow5.livedata; + +import ohos.aafwk.ability.ILifecycleObserver; + +public interface ILiveData extends ILifecycleObserver { + +} diff --git a/livedata/src/main/java/com/dbflow5/livedata/LiveData.java b/livedata/src/main/java/com/dbflow5/livedata/LiveData.java new file mode 100644 index 0000000000000000000000000000000000000000..ba697db9ba3f1af8a5813a87900f349b84fdfc36 --- /dev/null +++ b/livedata/src/main/java/com/dbflow5/livedata/LiveData.java @@ -0,0 +1,192 @@ +package com.dbflow5.livedata; + +import ohos.aafwk.ability.ILifecycle; +import ohos.aafwk.ability.Lifecycle; +import ohos.aafwk.ability.LifecycleStateObserver; +import ohos.aafwk.content.Intent; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 模拟实现原库的LiveData功能 + */ +public class LiveData implements ILiveData { + + private int mActiveCount = 0; + private boolean mDispatchingValue; + private boolean mDispatchInvalidated; + private volatile Object mData; + static final Object NOT_SET = new Object(); + private int mVersion; + static final int START_VERSION = -1; + private final Map, LifecycleBoundObserver> mObservers = new ConcurrentHashMap, LifecycleBoundObserver>(); + + public LiveData() { + mData = NOT_SET; + mVersion = START_VERSION; + } + + public LiveData(T value) { + mData = value; + mVersion = START_VERSION + 1; + } + + public void observe(ILifecycle owner, Observer observer) { + if (owner.getLifecycle().getLifecycleState() == Lifecycle.Event.ON_STOP) { + return; + } + LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); + if (mObservers.containsKey(observer)) { + LifecycleBoundObserver existing = mObservers.get(observer); + if (!existing.isAttachedTo(owner)) { + throw new IllegalArgumentException("Cannot add the same observer with different lifecycles"); + } + } else { + mObservers.put(observer, wrapper); + owner.getLifecycle().addObserver(wrapper); + } + + } + + public T getValue() { + return (T) mData; + } + + protected void setValue(T value) { + mVersion++; + mData = value; + dispatchingValue(null); + } + + public void removeObserver(final Observer observer) { + ObserverWrapper removed = mObservers.remove(observer); + if (removed == null) { + return; + } + removed.detachObserver(); + removed.activeStateChanged(false); + } + + void dispatchingValue(ObserverWrapper initiator) { + if (mDispatchingValue) { + mDispatchInvalidated = true; + return; + } + mDispatchingValue = true; + do { + mDispatchInvalidated = false; + if (initiator != null) { + considerNotify(initiator); + initiator = null; + } else { + for (ObserverWrapper wrapper : mObservers.values()) { + considerNotify(wrapper); + if (mDispatchInvalidated) { + break; + } + } + } + } while (mDispatchInvalidated); + mDispatchingValue = false; + } + + private void considerNotify(ObserverWrapper observer) { + if (!observer.mActive) { + return; + } + if (!observer.shouldBeActive()) { + observer.activeStateChanged(false); + return; + } + if (observer.mLastVersion >= mVersion) { + return; + } + observer.mLastVersion = mVersion; + //noinspection unchecked + observer.mObserver.onChanged((T) mData); + } + + /** + * 激活函数 + */ + protected void onActive() { + } + + /** + * 取消激活函数 + */ + protected void onInactive() { + } + + abstract class ObserverWrapper { + final Observer mObserver; + boolean mActive; + int mLastVersion = START_VERSION; + + protected ObserverWrapper(Observer mObserver) { + this.mObserver = mObserver; + } + + abstract boolean shouldBeActive(); + + boolean isAttachedTo(ILifecycle owner) { + return false; + } + + void detachObserver() { + } + + void activeStateChanged(boolean newActive) { + if (newActive == mActive) { + return; + } + + mActive = newActive; + boolean wasInactive = mActiveCount == 0; + mActiveCount += mActive ? 1 : -1; + if (wasInactive && mActive) { + onActive(); + } + if (mActiveCount == 0 && !mActive) { + onInactive(); + } + if (mActive) { + dispatchingValue(this); + } + } + } + + class LifecycleBoundObserver extends ObserverWrapper implements LifecycleStateObserver { + ILifecycle mOwner; + + public LifecycleBoundObserver(ILifecycle owner, Observer observer) { + super(observer); + mOwner = owner; + } + + @Override + boolean shouldBeActive() { + return mOwner.getLifecycle().getLifecycleState().compareTo(Lifecycle.Event.ON_INACTIVE) >= 0; + } + + @Override + boolean isAttachedTo(ILifecycle owner) { + return mOwner == owner; + } + + @Override + void detachObserver() { + mOwner.getLifecycle().removeObserver(this); + } + + @Override + public void onStateChanged(Lifecycle.Event event, Intent intent) { + if (mOwner.getLifecycle().getLifecycleState() == Lifecycle.Event.ON_STOP) { + removeObserver(mObserver); + return; + } + activeStateChanged(shouldBeActive()); + } + } +} diff --git a/livedata/src/main/java/com/dbflow5/livedata/Observer.java b/livedata/src/main/java/com/dbflow5/livedata/Observer.java new file mode 100644 index 0000000000000000000000000000000000000000..756af5b14a6e59f25ad64f8609c0c6a84c86480a --- /dev/null +++ b/livedata/src/main/java/com/dbflow5/livedata/Observer.java @@ -0,0 +1,5 @@ +package com.dbflow5.livedata; + +public interface Observer { + void onChanged(T t); +} diff --git a/livedata/src/main/java/com/dbflow5/livedata/QueryLiveData.java b/livedata/src/main/java/com/dbflow5/livedata/QueryLiveData.java new file mode 100644 index 0000000000000000000000000000000000000000..98dd27e6ca14a6f39edcc1bb1984ae2afa111fa9 --- /dev/null +++ b/livedata/src/main/java/com/dbflow5/livedata/QueryLiveData.java @@ -0,0 +1,91 @@ +package com.dbflow5.livedata; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.observing.OnTableChangedObserver; +import com.dbflow5.observing.TableObserver; +import com.dbflow5.query.ModelQueriable; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class QueryLiveData extends LiveData{ + private final ModelQueriable modelQueriable; + private final BiFunction, DatabaseWrapper, R> evalFn; + + private Set> associatedTables; + + private final OnTableChangedObserver onTableChangedObserver = new OnTableChangedObserver(new ArrayList<>(associatedTables)) { + @Override + protected void onChanged(Set> tables) { + if (!tables.isEmpty()) { + evaluateEmission(tables.stream().findFirst().get()); + } + } + }; + + public QueryLiveData(ModelQueriable modelQueriable, BiFunction, DatabaseWrapper, R> evalFn) { + this.modelQueriable = modelQueriable; + this.evalFn =evalFn; + + if(modelQueriable.extractFrom() != null) { + Set> setClasses = modelQueriable.extractFrom().associatedTables(); + if(setClasses == null){ + setClasses = new HashSet<>(Collections.singleton(modelQueriable.table())); + } + associatedTables = setClasses; + } + } + + private void evaluateEmission(Class table) { + if(table == null) { + table = modelQueriable.table(); + } + FlowManager.databaseForTable(table, db -> null) + .beginTransactionAsync(new Function() { + @Override + public R apply(DatabaseWrapper databaseWrapper) { + return evalFn.apply(modelQueriable, databaseWrapper); + } + }).execute(null, null, null, (rTransaction, r) -> { + setValue(r); + return null; + }); + } + + @Override + public void onActive() { + super.onActive(); + + DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables.stream().findFirst().get()); + // force initialize the db + db.getWritableDatabase(); + + TableObserver observer = db.tableObserver(); + observer.addOnTableChangedObserver(onTableChangedObserver); + + // trigger initial emission on active. + evaluateEmission(null); + } + + @Override + public void onInactive() { + super.onInactive(); + DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables.stream().findFirst().get()); + TableObserver observer = db.tableObserver(); + observer.removeOnTableChangedObserver(onTableChangedObserver); + } + + /** + * Return a new [LiveData] instance. Specify using the [evalFn] what query to run. + * + * @param q ModelQueriable + * @param evalFn BiFunction + * @return [LiveData] instance + */ + public static , R> LiveData toLiveData(Q q, BiFunction, DatabaseWrapper, R> evalFn) { + return new QueryLiveData<>(q, evalFn); + } +} diff --git a/livedata/src/main/kotlin/com/dbflow5/livedata/LiveData.kt b/livedata/src/main/kotlin/com/dbflow5/livedata/LiveData.kt deleted file mode 100644 index fc6cb69e06b707c3c52d74da1490b85a0efe4eba..0000000000000000000000000000000000000000 --- a/livedata/src/main/kotlin/com/dbflow5/livedata/LiveData.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.dbflow5.livedata - -import androidx.lifecycle.LiveData -import com.dbflow5.config.FlowManager -import com.dbflow5.config.databaseForTable -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.observing.OnTableChangedObserver -import com.dbflow5.query.ModelQueriable -import com.dbflow5.query.extractFrom -import kotlin.reflect.KClass - -/** - * Return a new [LiveData] instance. Specify using the [evalFn] what query to run. - */ -fun , R> Q.toLiveData(evalFn: ModelQueriable.(DatabaseWrapper) -> R): LiveData = - QueryLiveData(this, evalFn) - -class QueryLiveData(private val modelQueriable: ModelQueriable, - private val evalFn: ModelQueriable.(DatabaseWrapper) -> R) : LiveData() { - - private val associatedTables: Set> = modelQueriable.extractFrom()?.associatedTables - ?: setOf(modelQueriable.table) - - private val onTableChangedObserver = object : OnTableChangedObserver(associatedTables.toList()) { - override fun onChanged(tables: Set>) { - if (tables.isNotEmpty()) { - evaluateEmission(tables.first().kotlin) - } - } - } - - private fun evaluateEmission(table: KClass<*> = modelQueriable.table.kotlin) { - databaseForTable(table) - .beginTransactionAsync { modelQueriable.evalFn(it) } - .execute { _, r -> value = r } - } - - override fun onActive() { - super.onActive() - - val db = FlowManager.getDatabaseForTable(associatedTables.first()) - // force initialize the db - db.writableDatabase - - val observer = db.tableObserver - observer.addOnTableChangedObserver(onTableChangedObserver) - - // trigger initial emission on active. - evaluateEmission() - } - - override fun onInactive() { - super.onInactive() - val db = FlowManager.getDatabaseForTable(associatedTables.first()) - val observer = db.tableObserver - observer.removeOnTableChangedObserver(onTableChangedObserver) - } -} diff --git a/livedata/src/main/resources/base/element/string.json b/livedata/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8ac1dc3fad84e86eaf5cb57a3253866cf1532748 --- /dev/null +++ b/livedata/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "livedata_library", + "value": "livedata_library" + } + ] +} diff --git a/livedata/src/test/java/com/dbflow5/livedata/ExampleTest.java b/livedata/src/test/java/com/dbflow5/livedata/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a0c7524e4234f61d6147a71bf981e5481aeb84b4 --- /dev/null +++ b/livedata/src/test/java/com/dbflow5/livedata/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.livedata; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/paging/.gitignore b/paging/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/paging/.gitignore @@ -0,0 +1 @@ +/build diff --git a/paging/build.gradle b/paging/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ef62e9ed946eb6c6a7e799f989ec1a963ba67afb --- /dev/null +++ b/paging/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + implementation(project(":lib")) +} diff --git a/paging/build.gradle.kts b/paging/build.gradle.kts deleted file mode 100644 index 2bd3b4d004c43b1ec059e54a9c50e3e73feb63cd..0000000000000000000000000000000000000000 --- a/paging/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.ArchMin) - targetSdkVersion(Versions.TargetSdk) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - implementation(project(":lib")) - api(Dependencies.AndroidX.Paging) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/paging/consumer-rules.pro b/paging/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/paging/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/paging/gradle.properties b/paging/gradle.properties deleted file mode 100644 index f54abcf2a1130df3b37889aae260a14eb2b4dbcf..0000000000000000000000000000000000000000 --- a/paging/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-paging -bt_packaging=aar -bt_artifact_id=dbflow-paging \ No newline at end of file diff --git a/paging/proguard-rules.pro b/paging/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/paging/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/paging/settings.gradle b/paging/settings.gradle deleted file mode 100644 index 00126c83cd98c292680fc4b1685a6e5d292fb7c1..0000000000000000000000000000000000000000 --- a/paging/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = buildName \ No newline at end of file diff --git a/paging/src/main/AndroidManifest.xml b/paging/src/main/AndroidManifest.xml deleted file mode 100644 index bec1425d452787e8bd527636a1745b5c71adf729..0000000000000000000000000000000000000000 --- a/paging/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/paging/src/main/config.json b/paging/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..126479f3600bcc0f9356002f51ac2d97a38d0ebb --- /dev/null +++ b/paging/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.paging", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "paging", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/AnyThread.java b/paging/src/main/java/com/dbflow5/paging/AnyThread.java new file mode 100644 index 0000000000000000000000000000000000000000..3e35cbdc9aa25fedc7b9bcbc0d9ad74f0c1cdf0a --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/AnyThread.java @@ -0,0 +1,17 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface AnyThread { +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/ContiguousDataSource.java b/paging/src/main/java/com/dbflow5/paging/ContiguousDataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..2be3fc68a74a42a4bc9c8cffa8d24e46458a55b0 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/ContiguousDataSource.java @@ -0,0 +1,44 @@ +package com.dbflow5.paging; + +import java.util.concurrent.Executor; + +abstract class ContiguousDataSource extends DataSource { + @Override + boolean isContiguous() { + return true; + } + + abstract void dispatchLoadInitial( + @Nullable Key key, + int initialLoadSize, + int pageSize, + boolean enablePlaceholders, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver); + + abstract void dispatchLoadAfter( + int currentEndIndex, + @NonNull Value currentEndItem, + int pageSize, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver); + + abstract void dispatchLoadBefore( + int currentBeginIndex, + @NonNull Value currentBeginItem, + int pageSize, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver); + + /** + * Get the key from either the position, or item, or null if position/item invalid. + *

+ * Position may not match passed item's position - if trying to query the key from a position + * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed. + */ + abstract Key getKey(int position, Value item); + + boolean supportsPageDropping() { + return true; + } +} diff --git a/paging/src/main/java/com/dbflow5/paging/ContiguousPagedList.java b/paging/src/main/java/com/dbflow5/paging/ContiguousPagedList.java new file mode 100644 index 0000000000000000000000000000000000000000..e0dc830ea91d12e27856d67758d192e185f31970 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/ContiguousPagedList.java @@ -0,0 +1,391 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.util.List; +import java.util.concurrent.Executor; + +class ContiguousPagedList extends PagedList implements PagedStorage.Callback { + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final ContiguousDataSource mDataSource; + + @Retention(SOURCE) + @IntDef({READY_TO_FETCH, FETCHING, DONE_FETCHING}) + @interface FetchState {} + + private static final int READY_TO_FETCH = 0; + private static final int FETCHING = 1; + private static final int DONE_FETCHING = 2; + + @FetchState + @SuppressWarnings("WeakerAccess") /* synthetic access */ + int mPrependWorkerState = READY_TO_FETCH; + @FetchState + @SuppressWarnings("WeakerAccess") /* synthetic access */ + int mAppendWorkerState = READY_TO_FETCH; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + int mPrependItemsRequested = 0; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + int mAppendItemsRequested = 0; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + boolean mReplacePagesWithNulls = false; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final boolean mShouldTrim; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + PageResult.Receiver mReceiver = new PageResult.Receiver() { + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread + @Override + public void onPageResult(@PageResult.ResultType int resultType, + @NonNull PageResult pageResult) { + if (pageResult.isInvalid()) { + detach(); + return; + } + + if (isDetached()) { + // No op, have detached + return; + } + + List page = pageResult.page; + if (resultType == PageResult.INIT) { + mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, + pageResult.positionOffset, ContiguousPagedList.this); + if (mLastLoad == LAST_LOAD_UNSPECIFIED) { + // Because the ContiguousPagedList wasn't initialized with a last load position, + // initialize it to the middle of the initial load + mLastLoad = + pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2; + } + } else { + // if we end up trimming, we trim from side that's furthest from most recent access + boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange(); + + // is the new page big enough to warrant pre-trimming (i.e. dropping) it? + boolean skipNewPage = mShouldTrim + && mStorage.shouldPreTrimNewPage( + mConfig.maxSize, mRequiredRemainder, page.size()); + + if (resultType == PageResult.APPEND) { + if (skipNewPage && !trimFromFront) { + // don't append this data, drop it + mAppendItemsRequested = 0; + mAppendWorkerState = READY_TO_FETCH; + } else { + mStorage.appendPage(page, ContiguousPagedList.this); + } + } else if (resultType == PageResult.PREPEND) { + if (skipNewPage && trimFromFront) { + // don't append this data, drop it + mPrependItemsRequested = 0; + mPrependWorkerState = READY_TO_FETCH; + } else { + mStorage.prependPage(page, ContiguousPagedList.this); + } + } else { + throw new IllegalArgumentException("unexpected resultType " + resultType); + } + + if (mShouldTrim) { + if (trimFromFront) { + if (mPrependWorkerState != FETCHING) { + if (mStorage.trimFromFront( + mReplacePagesWithNulls, + mConfig.maxSize, + mRequiredRemainder, + ContiguousPagedList.this)) { + // trimmed from front, ensure we can fetch in that dir + mPrependWorkerState = READY_TO_FETCH; + } + } + } else { + if (mAppendWorkerState != FETCHING) { + if (mStorage.trimFromEnd( + mReplacePagesWithNulls, + mConfig.maxSize, + mRequiredRemainder, + ContiguousPagedList.this)) { + mAppendWorkerState = READY_TO_FETCH; + } + } + } + } + } + + if (mBoundaryCallback != null) { + boolean deferEmpty = mStorage.size() == 0; + boolean deferBegin = !deferEmpty + && resultType == PageResult.PREPEND + && pageResult.page.size() == 0; + boolean deferEnd = !deferEmpty + && resultType == PageResult.APPEND + && pageResult.page.size() == 0; + deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); + } + } + }; + + static final int LAST_LOAD_UNSPECIFIED = -1; + + ContiguousPagedList( + @NonNull ContiguousDataSource dataSource, + @NonNull Executor mainThreadExecutor, + @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback boundaryCallback, + @NonNull Config config, + final @Nullable K key, + int lastLoad) { + super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config); + mDataSource = dataSource; + mLastLoad = lastLoad; + + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.dispatchLoadInitial(key, + mConfig.initialLoadSizeHint, + mConfig.pageSize, + mConfig.enablePlaceholders, + mMainThreadExecutor, + mReceiver); + } + mShouldTrim = mDataSource.supportsPageDropping() + && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED; + } + + @MainThread + @Override + void dispatchUpdatesSinceSnapshot( + @NonNull PagedList pagedListSnapshot, @NonNull Callback callback) { + final PagedStorage snapshot = pagedListSnapshot.mStorage; + + final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended(); + final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended(); + + final int previousTrailing = snapshot.getTrailingNullCount(); + final int previousLeading = snapshot.getLeadingNullCount(); + + // Validate that the snapshot looks like a previous version of this list - if it's not, + // we can't be sure we'll dispatch callbacks safely + if (snapshot.isEmpty() + || newlyAppended < 0 + || newlyPrepended < 0 + || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0) + || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0) + || (mStorage.getStorageCount() + != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) { + throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" + + " to be a snapshot of this PagedList"); + } + + if (newlyAppended != 0) { + final int changedCount = Math.min(previousTrailing, newlyAppended); + final int addedCount = newlyAppended - changedCount; + + final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount(); + if (changedCount != 0) { + callback.onChanged(endPosition, changedCount); + } + if (addedCount != 0) { + callback.onInserted(endPosition + changedCount, addedCount); + } + } + if (newlyPrepended != 0) { + final int changedCount = Math.min(previousLeading, newlyPrepended); + final int addedCount = newlyPrepended - changedCount; + + if (changedCount != 0) { + callback.onChanged(previousLeading, changedCount); + } + if (addedCount != 0) { + callback.onInserted(0, addedCount); + } + } + } + + static int getPrependItemsRequested(int prefetchDistance, int index, int leadingNulls) { + return prefetchDistance - (index - leadingNulls); + } + + static int getAppendItemsRequested( + int prefetchDistance, int index, int itemsBeforeTrailingNulls) { + return index + prefetchDistance + 1 - itemsBeforeTrailingNulls; + } + + @MainThread + @Override + protected void loadAroundInternal(int index) { + int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index, + mStorage.getLeadingNullCount()); + int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index, + mStorage.getLeadingNullCount() + mStorage.getStorageCount()); + + mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested); + if (mPrependItemsRequested > 0) { + schedulePrepend(); + } + + mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested); + if (mAppendItemsRequested > 0) { + scheduleAppend(); + } + } + + @MainThread + private void schedulePrepend() { + if (mPrependWorkerState != READY_TO_FETCH) { + return; + } + mPrependWorkerState = FETCHING; + + final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset(); + + // safe to access first item here - mStorage can't be empty if we're prepending + final V item = mStorage.getFirstLoadedItem(); + mBackgroundThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (isDetached()) { + return; + } + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize, + mMainThreadExecutor, mReceiver); + } + } + }); + } + + @MainThread + private void scheduleAppend() { + if (mAppendWorkerState != READY_TO_FETCH) { + return; + } + mAppendWorkerState = FETCHING; + + final int position = mStorage.getLeadingNullCount() + + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset(); + + // safe to access first item here - mStorage can't be empty if we're appending + final V item = mStorage.getLastLoadedItem(); + mBackgroundThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (isDetached()) { + return; + } + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize, + mMainThreadExecutor, mReceiver); + } + } + }); + } + + @Override + boolean isContiguous() { + return true; + } + + @NonNull + @Override + public DataSource getDataSource() { + return mDataSource; + } + + @Nullable + @Override + public Object getLastKey() { + return mDataSource.getKey(mLastLoad, mLastItem); + } + + @MainThread + @Override + public void onInitialized(int count) { + notifyInserted(0, count); + // simple heuristic to decide if, when dropping pages, we should replace with placeholders + mReplacePagesWithNulls = + mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0; + } + + @MainThread + @Override + public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) { + // consider whether to post more work, now that a page is fully prepended + mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount; + mPrependWorkerState = READY_TO_FETCH; + if (mPrependItemsRequested > 0) { + // not done prepending, keep going + schedulePrepend(); + } + + // finally dispatch callbacks, after prepend may have already been scheduled + notifyChanged(leadingNulls, changedCount); + notifyInserted(0, addedCount); + + offsetAccessIndices(addedCount); + } + + @MainThread + @Override + public void onEmptyPrepend() { + mPrependWorkerState = DONE_FETCHING; + } + + @MainThread + @Override + public void onPageAppended(int endPosition, int changedCount, int addedCount) { + // consider whether to post more work, now that a page is fully appended + mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount; + mAppendWorkerState = READY_TO_FETCH; + if (mAppendItemsRequested > 0) { + // not done appending, keep going + scheduleAppend(); + } + + // finally dispatch callbacks, after append may have already been scheduled + notifyChanged(endPosition, changedCount); + notifyInserted(endPosition + changedCount, addedCount); + } + + @MainThread + @Override + public void onEmptyAppend() { + mAppendWorkerState = DONE_FETCHING; + } + + @MainThread + @Override + public void onPagePlaceholderInserted(int pageIndex) { + throw new IllegalStateException("Tiled callback on ContiguousPagedList"); + } + + @MainThread + @Override + public void onPageInserted(int start, int count) { + throw new IllegalStateException("Tiled callback on ContiguousPagedList"); + } + + @Override + public void onPagesRemoved(int startOfDrops, int count) { + notifyRemoved(startOfDrops, count); + } + + @Override + public void onPagesSwappedToPlaceholder(int startOfDrops, int count) { + notifyChanged(startOfDrops, count); + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/DataSource.java b/paging/src/main/java/com/dbflow5/paging/DataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..c67e7690e3480fa615113e7f9e0990beeb1f8028 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/DataSource.java @@ -0,0 +1,179 @@ +package com.dbflow5.paging; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +@SuppressWarnings("unused") +public abstract class DataSource { + public abstract static class Factory { + @NonNull + public abstract DataSource create(); + + @NonNull + public DataSource.Factory map( + @NonNull Function function) { + return mapByPage(createListFunction(function)); + } + + @NonNull + public DataSource.Factory mapByPage( + @NonNull final Function, List> function) { + return new Factory() { + @Override + public DataSource create() { + return Factory.this.create().mapByPage(function); + } + }; + } + } + + @NonNull + static Function, List> createListFunction( + final @NonNull Function innerFunc) { + return source -> { + List out = new ArrayList<>(source.size()); + for (X x : source) { + out.add(innerFunc.apply(x)); + } + return out; + }; + } + + static List convert(Function, List> function, List source) { + List dest = function.apply(source); + if (dest.size() != source.size()) { + throw new IllegalStateException("Invalid Function " + function + + " changed return size. This is not supported."); + } + return dest; + } + + DataSource() { + } + + @NonNull + public abstract DataSource mapByPage( + @NonNull Function, List> function); + + @NonNull + public abstract DataSource map( + @NonNull Function function); + + abstract boolean isContiguous(); + + static class LoadCallbackHelper { + static void validateInitialLoadParams(@NonNull List data, int position, int totalCount) { + if (position < 0) { + throw new IllegalArgumentException("Position must be non-negative"); + } + if (data.size() + position > totalCount) { + throw new IllegalArgumentException( + "List size + position too large, last item in list beyond totalCount."); + } + if (data.size() == 0 && totalCount > 0) { + throw new IllegalArgumentException( + "Initial result cannot be empty if items are present in data set."); + } + } + + @PageResult.ResultType + final int mResultType; + private final DataSource mDataSource; + final PageResult.Receiver mReceiver; + + // mSignalLock protects mPostExecutor, and mHasSignalled + private final Object mSignalLock = new Object(); + private Executor mPostExecutor = null; + private boolean mHasSignalled = false; + + LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType, + @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver receiver) { + mDataSource = dataSource; + mResultType = resultType; + mPostExecutor = mainThreadExecutor; + mReceiver = receiver; + } + + void setPostExecutor(Executor postExecutor) { + synchronized (mSignalLock) { + mPostExecutor = postExecutor; + } + } + + boolean dispatchInvalidResultIfInvalid() { + if (mDataSource.isInvalid()) { + dispatchResultToReceiver(PageResult.getInvalidResult()); + return true; + } + return false; + } + + void dispatchResultToReceiver(final @NonNull PageResult result) { + Executor executor; + synchronized (mSignalLock) { + if (mHasSignalled) { + throw new IllegalStateException( + "callback.onResult already called, cannot call again."); + } + mHasSignalled = true; + executor = mPostExecutor; + } + + if (executor != null) { + executor.execute(new Runnable() { + @Override + public void run() { + mReceiver.onPageResult(mResultType, result); + } + }); + } else { + mReceiver.onPageResult(mResultType, result); + } + } + } + + public interface InvalidatedCallback { + @AnyThread + void onInvalidated(); + } + + private final AtomicBoolean mInvalid = new AtomicBoolean(false); + + private final CopyOnWriteArrayList mOnInvalidatedCallbacks = + new CopyOnWriteArrayList<>(); + + @AnyThread + @SuppressWarnings("WeakerAccess") + public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + mOnInvalidatedCallbacks.add(onInvalidatedCallback); + } + + @AnyThread + @SuppressWarnings("WeakerAccess") + public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + mOnInvalidatedCallbacks.remove(onInvalidatedCallback); + } + + @AnyThread + public void invalidate() { + if (mInvalid.compareAndSet(false, true)) { + for (InvalidatedCallback callback : mOnInvalidatedCallbacks) { + callback.onInvalidated(); + } + } + } + + /** + * Returns true if the data source is invalid, and can no longer be queried for data. + * + * @return True if the data source is invalid, and can no longer return data. + */ + @WorkerThread + public boolean isInvalid() { + return mInvalid.get(); + } +} diff --git a/paging/src/main/java/com/dbflow5/paging/IntDef.java b/paging/src/main/java/com/dbflow5/paging/IntDef.java new file mode 100644 index 0000000000000000000000000000000000000000..e03b89627c3477cacf429b9e6f163cb94d533f36 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/IntDef.java @@ -0,0 +1,30 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(SOURCE) +@Target({ANNOTATION_TYPE}) +public @interface IntDef { + /** + * Defines the allowed constants for this element + */ + int[] value() default {}; + + /** + * Defines whether the constants can be used as a flag, or just as an enum (the default) + */ + boolean flag() default false; + + /** + * Whether any other values are allowed. Normally this is + * not the case, but this allows you to specify a set of + * expected constants, which helps code completion in the IDE + * and documentation generation and so on, but without + * flagging compilation warnings if other values are specified. + */ + boolean open() default false; +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/IntRange.java b/paging/src/main/java/com/dbflow5/paging/IntRange.java new file mode 100644 index 0000000000000000000000000000000000000000..a73d09f0319ba0d6079887a638cfaf43da046ae5 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/IntRange.java @@ -0,0 +1,31 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that the annotated element should be an int or long in the given range + *

+ * Example: + *


+ *  @IntRange(from=0,to=255)
+ *  public int getAlpha() {
+ *      ...
+ *  }
+ * 
+ */ +@Retention(CLASS) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) +public @interface IntRange { + /** Smallest value, inclusive */ + long from() default Long.MIN_VALUE; + /** Largest value, inclusive */ + long to() default Long.MAX_VALUE; +} diff --git a/paging/src/main/java/com/dbflow5/paging/MainThread.java b/paging/src/main/java/com/dbflow5/paging/MainThread.java new file mode 100644 index 0000000000000000000000000000000000000000..76845b419a3b5f8299b3f562666e305e5c3dd273 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/MainThread.java @@ -0,0 +1,17 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface MainThread { +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/NonNull.java b/paging/src/main/java/com/dbflow5/paging/NonNull.java new file mode 100644 index 0000000000000000000000000000000000000000..2c20c41974d38900014c881a94ec5c5a2cc922b3 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/NonNull.java @@ -0,0 +1,9 @@ +package com.dbflow5.paging; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE}) +public @interface NonNull { +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/Nullable.java b/paging/src/main/java/com/dbflow5/paging/Nullable.java new file mode 100644 index 0000000000000000000000000000000000000000..3a308fcf851c10eba7584e74a9eba8138d5cb49a --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/Nullable.java @@ -0,0 +1,31 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that a parameter, field or method return value can be null. + *

+ * When decorating a method call parameter, this denotes that the parameter can + * legitimately be null and the method will gracefully deal with it. Typically + * used on optional parameters. + *

+ * When decorating a method, this denotes the method might legitimately return + * null. + *

+ * This is a marker annotation and it has no specific attributes. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) +public @interface Nullable { +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/PageList.java b/paging/src/main/java/com/dbflow5/paging/PageList.java new file mode 100644 index 0000000000000000000000000000000000000000..161c8c141bf5b0bff72393364025fbab36b7ae4b --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/PageList.java @@ -0,0 +1,978 @@ +package com.dbflow5.paging; + +import java.lang.ref.WeakReference; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +abstract class PagedList extends AbstractList { + @NonNull + final Executor mMainThreadExecutor; + @NonNull + final Executor mBackgroundThreadExecutor; + @Nullable + final BoundaryCallback mBoundaryCallback; + @NonNull + final Config mConfig; + @NonNull + final PagedStorage mStorage; + + /** + * Last access location, in total position space (including offset). + *

+ * Used by positional data + * sources to initialize loading near viewport + */ + int mLastLoad = 0; + T mLastItem = null; + + final int mRequiredRemainder; + + // if set to true, mBoundaryCallback is non-null, and should + // be dispatched when nearby load has occurred + @SuppressWarnings("WeakerAccess") /* synthetic access */ + boolean mBoundaryCallbackBeginDeferred = false; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + boolean mBoundaryCallbackEndDeferred = false; + + // lowest and highest index accessed by loadAround. Used to + // decide when mBoundaryCallback should be dispatched + private int mLowestIndexAccessed = Integer.MAX_VALUE; + private int mHighestIndexAccessed = Integer.MIN_VALUE; + + private final AtomicBoolean mDetached = new AtomicBoolean(false); + + private final ArrayList> mCallbacks = new ArrayList<>(); + + PagedList(@NonNull PagedStorage storage, + @NonNull Executor mainThreadExecutor, + @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback boundaryCallback, + @NonNull Config config) { + mStorage = storage; + mMainThreadExecutor = mainThreadExecutor; + mBackgroundThreadExecutor = backgroundThreadExecutor; + mBoundaryCallback = boundaryCallback; + mConfig = config; + mRequiredRemainder = mConfig.prefetchDistance * 2 + mConfig.pageSize; + } + + /** + * Create a PagedList which loads data from the provided data source on a background thread, + * posting updates to the main thread. + * + * + * @param dataSource DataSource providing data to the PagedList + * @param notifyExecutor Thread that will use and consume data from the PagedList. + * Generally, this is the UI/main thread. + * @param fetchExecutor Data loading will be done via this executor - + * should be a background thread. + * @param boundaryCallback Optional boundary callback to attach to the list. + * @param config PagedList Config, which defines how the PagedList will load data. + * @param Key type that indicates to the DataSource what data to load. + * @param Type of items to be held and loaded by the PagedList. + * + * @return Newly created PagedList, which will page in data from the DataSource as needed. + */ + @NonNull + @SuppressWarnings("WeakerAccess") /* synthetic access */ + static PagedList create(@NonNull DataSource dataSource, + @NonNull Executor notifyExecutor, + @NonNull Executor fetchExecutor, + @Nullable BoundaryCallback boundaryCallback, + @NonNull Config config, + @Nullable K key) { + if (dataSource.isContiguous() || !config.enablePlaceholders) { + int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED; + if (!dataSource.isContiguous()) { + //noinspection unchecked + dataSource = (DataSource) ((PositionalDataSource) dataSource) + .wrapAsContiguousWithoutPlaceholders(); + if (key != null) { + lastLoad = (Integer) key; + } + } + ContiguousDataSource contigDataSource = (ContiguousDataSource) dataSource; + return new ContiguousPagedList<>(contigDataSource, + notifyExecutor, + fetchExecutor, + boundaryCallback, + config, + key, + lastLoad); + } else { + return new TiledPagedList<>((PositionalDataSource) dataSource, + notifyExecutor, + fetchExecutor, + boundaryCallback, + config, + (key != null) ? (Integer) key : 0); + } + } + + /** + * Builder class for PagedList. + *

+ * DataSource, Config, main thread and background executor must all be provided. + *

+ * A PagedList queries initial data from its DataSource during construction, to avoid empty + * PagedLists being presented to the UI when possible. It's preferred to present initial data, + * so that the UI doesn't show an empty list, or placeholders for a few frames, just before + * showing initial content. + *

+ * {@link } does this creation on a background thread automatically, if you + * want to receive a {@code LiveData>}. + * + * @param Type of key used to load data from the DataSource. + * @param Type of items held and loaded by the PagedList. + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private final DataSource mDataSource; + private final Config mConfig; + private Executor mNotifyExecutor; + private Executor mFetchExecutor; + private BoundaryCallback mBoundaryCallback; + private Key mInitialKey; + + /** + * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}. + * + * @param dataSource DataSource the PagedList will load from. + * @param config Config that defines how the PagedList loads data from its DataSource. + */ + public Builder(@NonNull DataSource dataSource, @NonNull Config config) { + //noinspection ConstantConditions + if (dataSource == null) { + throw new IllegalArgumentException("DataSource may not be null"); + } + //noinspection ConstantConditions + if (config == null) { + throw new IllegalArgumentException("Config may not be null"); + } + mDataSource = dataSource; + mConfig = config; + } + + /** + * Create a PagedList.Builder with the provided {@link DataSource} and page size. + *

+ * This method is a convenience for: + *

+         * PagedList.Builder(dataSource,
+         *         new PagedList.Config.Builder().setPageSize(pageSize).build());
+         * 
+ * + * @param dataSource DataSource the PagedList will load from. + * @param pageSize Config that defines how the PagedList loads data from its DataSource. + */ + public Builder(@NonNull DataSource dataSource, int pageSize) { + this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build()); + } + /** + * The executor defining where page loading updates are dispatched. + * + * @param notifyExecutor Executor that receives PagedList updates, and where + * {@link Callback} calls are dispatched. Generally, this is the ui/main thread. + * @return this + */ + @NonNull + public Builder setNotifyExecutor(@NonNull Executor notifyExecutor) { + mNotifyExecutor = notifyExecutor; + return this; + } + + /** + * The executor used to fetch additional pages from the DataSource. + * + * Does not affect initial load, which will be done immediately on whichever thread the + * PagedList is created on. + * + * @param fetchExecutor Executor used to fetch from DataSources, generally a background + * thread pool for e.g. I/O or network loading. + * @return this + */ + @NonNull + public Builder setFetchExecutor(@NonNull Executor fetchExecutor) { + mFetchExecutor = fetchExecutor; + return this; + } + + /** + * The BoundaryCallback for out of data events. + *

+ * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. + * + * @param boundaryCallback BoundaryCallback for listening to out-of-data events. + * @return this + */ + @SuppressWarnings("unused") + @NonNull + public Builder setBoundaryCallback( + @Nullable BoundaryCallback boundaryCallback) { + mBoundaryCallback = boundaryCallback; + return this; + } + + /** + * Sets the initial key the DataSource should load around as part of initialization. + * + * @param initialKey Key the DataSource should load around as part of initialization. + * @return this + */ + @NonNull + public Builder setInitialKey(@Nullable Key initialKey) { + mInitialKey = initialKey; + return this; + } + + /** + * Creates a {@link PagedList} with the given parameters. + *

+ * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a + * DataSource posts all of its work (e.g. to a network thread), the PagedList will + * be immediately created as empty, and grow to its initial size when the initial load + * completes. + *

+ * If the DataSource implements its load synchronously, doing the load work immediately in + * the loadInitial method, the PagedList will block on that load before completing + * construction. In this case, use a background thread to create a PagedList. + *

+ * It's fine to create a PagedList with an async DataSource on the main thread, such as in + * the constructor of a ViewModel. An async network load won't block the initialLoad + * function. For a synchronous DataSource such as one created from a Room database, a + * {@code LiveData} can be safely constructed with {@link } + * on the main thread, since actual construction work is deferred, and done on a background + * thread. + *

+ * While build() will always return a PagedList, it's important to note that the PagedList + * initial load may fail to acquire data from the DataSource. This can happen for example if + * the DataSource is invalidated during its initial load. If this happens, the PagedList + * will be immediately {@link PagedList#isDetached() detached}, and you can retry + * construction (including setting a new DataSource). + * + * @return The newly constructed PagedList + */ + @WorkerThread + @NonNull + public PagedList build() { + // TODO: define defaults, once they can be used in module without android dependency + if (mNotifyExecutor == null) { + throw new IllegalArgumentException("MainThreadExecutor required"); + } + if (mFetchExecutor == null) { + throw new IllegalArgumentException("BackgroundThreadExecutor required"); + } + + //noinspection unchecked + return PagedList.create( + mDataSource, + mNotifyExecutor, + mFetchExecutor, + mBoundaryCallback, + mConfig, + mInitialKey); + } + } + + /** + * Get the item in the list of loaded items at the provided index. + * + * @param index Index in the loaded item list. Must be >= 0, and < {@link #size()} + * @return The item at the passed index, or null if a null placeholder is at the specified + * position. + * + * @see #size() + */ + @Override + @Nullable + public T get(int index) { + T item = mStorage.get(index); + if (item != null) { + mLastItem = item; + } + return item; + } + + /** + * Load adjacent items to passed index. + * + * @param index Index at which to load. + */ + public void loadAround(int index) { + if (index < 0 || index >= size()) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); + } + + mLastLoad = index + getPositionOffset(); + loadAroundInternal(index); + + mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index); + mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index); + + /* + * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to + * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded, + * and accesses happen near the boundaries. + * + * Note: we post here, since RecyclerView may want to add items in response, and this + * call occurs in PagedListAdapter bind. + */ + tryDispatchBoundaryCallbacks(true); + } + + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread + void deferBoundaryCallbacks(final boolean deferEmpty, + final boolean deferBegin, final boolean deferEnd) { + if (mBoundaryCallback == null) { + throw new IllegalStateException("Can't defer BoundaryCallback, no instance"); + } + + /* + * If lowest/highest haven't been initialized, set them to storage size, + * since placeholders must already be computed by this point. + * + * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately + * if the initial load size is smaller than the prefetch window (see + * TiledPagedListTest#boundaryCallback_immediate()) + */ + if (mLowestIndexAccessed == Integer.MAX_VALUE) { + mLowestIndexAccessed = mStorage.size(); + } + if (mHighestIndexAccessed == Integer.MIN_VALUE) { + mHighestIndexAccessed = 0; + } + + if (deferEmpty || deferBegin || deferEnd) { + // Post to the main thread, since we may be on creation thread currently + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + // on is dispatched immediately, since items won't be accessed + //noinspection ConstantConditions + if (deferEmpty) { + mBoundaryCallback.onZeroItemsLoaded(); + } + + // for other callbacks, mark deferred, and only dispatch if loadAround + // has been called near to the position + if (deferBegin) { + mBoundaryCallbackBeginDeferred = true; + } + if (deferEnd) { + mBoundaryCallbackEndDeferred = true; + } + tryDispatchBoundaryCallbacks(false); + } + }); + } + } + + /** + * Call this when mLowest/HighestIndexAccessed are changed, or + * mBoundaryCallbackBegin/EndDeferred is set. + */ + @SuppressWarnings("WeakerAccess") /* synthetic access */ + void tryDispatchBoundaryCallbacks(boolean post) { + final boolean dispatchBegin = mBoundaryCallbackBeginDeferred + && mLowestIndexAccessed <= mConfig.prefetchDistance; + final boolean dispatchEnd = mBoundaryCallbackEndDeferred + && mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance; + + if (!dispatchBegin && !dispatchEnd) { + return; + } + + if (dispatchBegin) { + mBoundaryCallbackBeginDeferred = false; + } + if (dispatchEnd) { + mBoundaryCallbackEndDeferred = false; + } + if (post) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); + } + }); + } else { + dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); + } + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + void dispatchBoundaryCallbacks(boolean begin, boolean end) { + // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present + if (begin) { + //noinspection ConstantConditions + mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem()); + } + if (end) { + //noinspection ConstantConditions + mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem()); + } + } + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + void offsetAccessIndices(int offset) { + // update last loadAround index + mLastLoad += offset; + + // update access range + mLowestIndexAccessed += offset; + mHighestIndexAccessed += offset; + } + + /** + * Returns size of the list, including any not-yet-loaded null padding. + * + * To get the number of loaded items, not counting placeholders, use {@link #getLoadedCount()}. + * + * @return Current total size of the list, including placeholders. + * + * @see #getLoadedCount() + */ + @Override + public int size() { + return mStorage.size(); + } + + /** + * Returns the number of items loaded in the PagedList. + * + * Unlike {@link #size()} this counts only loaded items, not placeholders. + *

+ * If placeholders are {@link Config#enablePlaceholders disabled}, this method is equivalent to + * {@link #size()}. + * + * @return Number of items currently loaded, not counting placeholders. + * + * @see #size() + */ + public int getLoadedCount() { + return mStorage.getLoadedCount(); + } + + /** + * Returns whether the list is immutable. + * + * Immutable lists may not become mutable again, and may safely be accessed from any thread. + *

+ * In the future, this method may return true when a PagedList has completed loading from its + * DataSource. Currently, it is equivalent to {@link #isDetached()}. + * + * @return True if the PagedList is immutable. + */ + @SuppressWarnings("WeakerAccess") + public boolean isImmutable() { + return isDetached(); + } + + /** + * Returns an immutable snapshot of the PagedList in its current state. + * + * If this PagedList {@link #isImmutable() is immutable} due to its DataSource being invalid, it + * will be returned. + * + * @return Immutable snapshot of PagedList data. + */ + @SuppressWarnings("WeakerAccess") + @NonNull + public List snapshot() { + if (isImmutable()) { + return this; + } + return new SnapshotPagedList<>(this); + } + + abstract boolean isContiguous(); + + /** + * Return the Config used to construct this PagedList. + * + * @return the Config of this PagedList + */ + @NonNull + public Config getConfig() { + return mConfig; + } + + /** + * Return the DataSource that provides data to this PagedList. + * + * @return the DataSource of this PagedList. + */ + @NonNull + public abstract DataSource getDataSource(); + + /** + * Return the key for the position passed most recently to {@link #loadAround(int)}. + *

+ * + * @return Key of position most recently passed to {@link #loadAround(int)}. + */ + @Nullable + public abstract Object getLastKey(); + + /** + * True if the PagedList has detached the DataSource it was loading from, and will no longer + * load new data. + *

+ * A detached list is {@link #isImmutable() immutable}. + * + * @return True if the data source is detached. + */ + @SuppressWarnings("WeakerAccess") + public boolean isDetached() { + return mDetached.get(); + } + + /** + * Detach the PagedList from its DataSource, and attempt to load no more data. + *

+ * This is called automatically when a DataSource load returns null, which is a + * signal to stop loading. The PagedList will continue to present existing data, but will not + * initiate new loads. + */ + @SuppressWarnings("WeakerAccess") + public void detach() { + mDetached.set(true); + } + + /** + * Position offset of the data in the list. + *

+ * If data is supplied by a {@link PositionalDataSource}, the item returned from + * get(i) has a position of i + getPositionOffset(). + *

+ */ + public int getPositionOffset() { + return mStorage.getPositionOffset(); + } + + /** + * Adds a callback, and issues updates since the previousSnapshot was created. + *

+ * If previousSnapshot is passed, the callback will also immediately be dispatched any + * differences between the previous snapshot, and the current state. For example, if the + * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls, + * 12 items, 3 nulls, the callback would immediately receive a call of + * onChanged(14, 2). + *

+ * This allows an observer that's currently presenting a snapshot to catch up to the most recent + * version, including any changes that may have been made. + *

+ * + * @param previousSnapshot Snapshot previously captured from this List, or null. + * @param callback Callback to dispatch to. + * + * @see #removeWeakCallback(Callback) + */ + @SuppressWarnings("WeakerAccess") + public void addWeakCallback(@Nullable List previousSnapshot, @NonNull Callback callback) { + if (previousSnapshot != null && previousSnapshot != this) { + + if (previousSnapshot.isEmpty()) { + if (!mStorage.isEmpty()) { + // If snapshot is empty, diff is trivial - just notify number new items. + // Note: occurs in async init, when snapshot taken before init page arrives + callback.onInserted(0, mStorage.size()); + } + } else { + PagedList storageSnapshot = (PagedList) previousSnapshot; + + //noinspection unchecked + dispatchUpdatesSinceSnapshot(storageSnapshot, callback); + } + } + + // first, clean up any empty weak refs + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + final Callback currentCallback = mCallbacks.get(i).get(); + if (currentCallback == null) { + mCallbacks.remove(i); + } + } + + // then add the new one + mCallbacks.add(new WeakReference<>(callback)); + } + /** + * Removes a previously added callback. + * + * @param callback Callback, previously added. + * @see #addWeakCallback(List, Callback) + */ + @SuppressWarnings("WeakerAccess") + public void removeWeakCallback(@NonNull Callback callback) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + final Callback currentCallback = mCallbacks.get(i).get(); + if (currentCallback == null || currentCallback == callback) { + // found callback, or empty weak ref + mCallbacks.remove(i); + } + } + } + + void notifyInserted(int position, int count) { + if (count != 0) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + final Callback callback = mCallbacks.get(i).get(); + if (callback != null) { + callback.onInserted(position, count); + } + } + } + } + + void notifyChanged(int position, int count) { + if (count != 0) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + final Callback callback = mCallbacks.get(i).get(); + + if (callback != null) { + callback.onChanged(position, count); + } + } + } + } + + void notifyRemoved(int position, int count) { + if (count != 0) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + final Callback callback = mCallbacks.get(i).get(); + + if (callback != null) { + callback.onRemoved(position, count); + } + } + } + } + + /** + * Dispatch updates since the non-empty snapshot was taken. + * + * @param snapshot Non-empty snapshot. + * @param callback Callback for updates that have occurred since snapshot. + */ + abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList snapshot, + @NonNull Callback callback); + + abstract void loadAroundInternal(int index); + + /** + * Callback signaling when content is loaded into the list. + *

+ * Can be used to listen to items being paged in and out. These calls will be dispatched on + * the executor defined by {@link Builder#setNotifyExecutor(Executor)}, which is generally + * the main/UI thread. + */ + public abstract static class Callback { + /** + * Called when null padding items have been loaded to signal newly available data, or when + * data that hasn't been used in a while has been dropped, and swapped back to null. + * + * @param position Position of first newly loaded items, out of total number of items + * (including padded nulls). + * @param count Number of items loaded. + */ + public abstract void onChanged(int position, int count); + + /** + * Called when new items have been loaded at the end or beginning of the list. + * + * @param position Position of the first newly loaded item (in practice, either + * 0 or size - 1. + * @param count Number of items loaded. + */ + public abstract void onInserted(int position, int count); + + /** + * Called when items have been removed at the end or beginning of the list, and have not + * been replaced by padded nulls. + * + * @param position Position of the first newly loaded item (in practice, either + * 0 or size - 1. + * @param count Number of items loaded. + */ + @SuppressWarnings("unused") + public abstract void onRemoved(int position, int count); + } + + /** + * Configures how a PagedList loads content from its DataSource. + *

+ * Use a Config {@link Builder} to construct and define custom loading behavior, such as + * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}. + */ + public static class Config { + /** + * When {@link #maxSize} is set to {@code MAX_SIZE_UNBOUNDED}, the maximum number of items + * loaded is unbounded, and pages will never be dropped. + */ + @SuppressWarnings("WeakerAccess") + public static final int MAX_SIZE_UNBOUNDED = Integer.MAX_VALUE; + + /** + * Size of each page loaded by the PagedList. + */ + public final int pageSize; + + /** + * Prefetch distance which defines how far ahead to load. + *

+ * If this value is set to 50, the paged list will attempt to load 50 items in advance of + * data that's already been accessed. + * + * @see PagedList#loadAround(int) + */ + @SuppressWarnings("WeakerAccess") + public final int prefetchDistance; + + /** + * Defines whether the PagedList may display null placeholders, if the DataSource provides + * them. + */ + @SuppressWarnings("WeakerAccess") + public final boolean enablePlaceholders; + + /** + * Defines the maximum number of items that may be loaded into this pagedList before pages + * should be dropped. + *

+ * loading from a {@code PageKeyedDataSource}, this value is ignored. + * + * @see #MAX_SIZE_UNBOUNDED + * @see Builder#setMaxSize(int) + */ + public final int maxSize; + + /** + * Size hint for initial load of PagedList, often larger than a regular page. + */ + @SuppressWarnings("WeakerAccess") + public final int initialLoadSizeHint; + + Config(int pageSize, int prefetchDistance, + boolean enablePlaceholders, int initialLoadSizeHint, int maxSize) { + this.pageSize = pageSize; + this.prefetchDistance = prefetchDistance; + this.enablePlaceholders = enablePlaceholders; + this.initialLoadSizeHint = initialLoadSizeHint; + this.maxSize = maxSize; + } + + /** + * Builder class for {@link Config}. + *

+ * You must at minimum specify page size with {@link #setPageSize(int)}. + */ + public static final class Builder { + static final int DEFAULT_INITIAL_PAGE_MULTIPLIER = 3; + + private int mPageSize = -1; + private int mPrefetchDistance = -1; + private int mInitialLoadSizeHint = -1; + private boolean mEnablePlaceholders = true; + private int mMaxSize = MAX_SIZE_UNBOUNDED; + + /** + * Defines the number of items loaded at once from the DataSource. + *

+ * Should be several times the number of visible items onscreen. + *

+ * Configuring your page size depends on how your data is being loaded and used. Smaller + * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally + * improve loading throughput, to a point + * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost). + *

+ * If you're loading data for very large, social-media style cards that take up most of + * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're + * displaying dozens of items in a tiled grid, which can present items during a scroll + * much more quickly, consider closer to 100. + * + * @param pageSize Number of items loaded at once from the DataSource. + * @return this + */ + @NonNull + public Builder setPageSize(@IntRange(from = 1) int pageSize) { + if (pageSize < 1) { + throw new IllegalArgumentException("Page size must be a positive number"); + } + mPageSize = pageSize; + return this; + } + + /** + * Defines how far from the edge of loaded content an access must be to trigger further + * loading. + *

+ * Should be several times the number of visible items onscreen. + *

+ * If not set, defaults to page size. + *

+ * A value of 0 indicates that no list items will be loaded until they are specifically + * requested. This is generally not recommended, so that users don't observe a + * placeholder item (with placeholders) or end of list (without) while scrolling. + * + * @param prefetchDistance Distance the PagedList should prefetch. + * @return this + */ + @NonNull + public Builder setPrefetchDistance(@IntRange(from = 0) int prefetchDistance) { + mPrefetchDistance = prefetchDistance; + return this; + } + + /** + * Pass false to disable null placeholders in PagedLists using this Config. + *

+ * If not set, defaults to true. + *

+ * A PagedList will present null placeholders for not-yet-loaded content if two + * conditions are met: + *

+ * 1) Its DataSource can count all unloaded items (so that the number of nulls to + * present is known). + *

+ * 2) placeholders are not disabled on the Config. + *

+ * + * @param enablePlaceholders False if null placeholders should be disabled. + * @return this + */ + @SuppressWarnings("SameParameterValue") + @NonNull + public Builder setEnablePlaceholders(boolean enablePlaceholders) { + mEnablePlaceholders = enablePlaceholders; + return this; + } + + /** + * Defines how many items to load when first load occurs. + *

+ * This value is typically larger than page size, so on first load data there's a large + * enough range of content loaded to cover small scrolls. + *

+ * When using a {@link PositionalDataSource}, the initial load size will be coerced to + * an integer multiple of pageSize, to enable efficient tiling. + *

+ * If not set, defaults to three times page size. + * + * @param initialLoadSizeHint Number of items to load while initializing the PagedList. + * @return this + */ + @SuppressWarnings("WeakerAccess") + @NonNull + public Builder setInitialLoadSizeHint(@IntRange(from = 1) int initialLoadSizeHint) { + mInitialLoadSizeHint = initialLoadSizeHint; + return this; + } + + /** + * Defines how many items to keep loaded at once. + *

+ * This can be used to cap the number of items kept in memory by dropping pages. This + * value is typically many pages so old pages are cached in case the user scrolls back. + *

+ * This value must be at least two times the + * {@link #setPrefetchDistance(int)} prefetch distance} plus the + * {@link #setPageSize(int) page size}). This constraint prevent loads from being + * continuously fetched and discarded due to prefetching. + *

+ * The max size specified here best effort, not a guarantee. In practice, if maxSize + * is many times the page size, the number of items held by the PagedList will not grow + * above this number. Exceptions are made as necessary to guarantee: + *

    + *
  • Pages are never dropped until there are more than two pages loaded. Note that + * a DataSource may not be held strictly to + * {@link Config#pageSize requested pageSize}, so two pages may be larger than + * expected. + *
  • Pages are never dropped if they are within a prefetch window (defined to be + * {@code pageSize + (2 * prefetchDistance)}) of the most recent load. + *
+ *

+ * {@link } does not currently support dropping pages - when + * loading from a {@code PageKeyedDataSource}, this value is ignored. + *

+ * If not set, defaults to {@code MAX_SIZE_UNBOUNDED}, which disables page dropping. + * + * @param maxSize Maximum number of items to keep in memory, or + * {@code MAX_SIZE_UNBOUNDED} to disable page dropping. + * @return this + * + * @see Config#MAX_SIZE_UNBOUNDED + * @see Config#maxSize + */ + @NonNull + public Builder setMaxSize(@IntRange(from = 2) int maxSize) { + mMaxSize = maxSize; + return this; + } + + /** + * Creates a {@link Config} with the given parameters. + * + * @return A new Config. + */ + @NonNull + public Config build() { + if (mPrefetchDistance < 0) { + mPrefetchDistance = mPageSize; + } + if (mInitialLoadSizeHint < 0) { + mInitialLoadSizeHint = mPageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER; + } + if (!mEnablePlaceholders && mPrefetchDistance == 0) { + throw new IllegalArgumentException("Placeholders and prefetch are the only ways" + + " to trigger loading of more data in the PagedList, so either" + + " placeholders must be enabled, or prefetch distance must be > 0."); + } + if (mMaxSize != MAX_SIZE_UNBOUNDED) { + if (mMaxSize < mPageSize + mPrefetchDistance * 2) { + throw new IllegalArgumentException("Maximum size must be at least" + + " pageSize + 2*prefetchDist, pageSize=" + mPageSize + + ", prefetchDist=" + mPrefetchDistance + ", maxSize=" + mMaxSize); + } + } + + return new Config(mPageSize, mPrefetchDistance, + mEnablePlaceholders, mInitialLoadSizeHint, mMaxSize); + } + } + } + + @MainThread + public abstract static class BoundaryCallback { + /** + * Called when zero items are returned from an initial load of the PagedList's data source. + */ + public void onZeroItemsLoaded() {} + + /** + * Called when the item at the front of the PagedList has been loaded, and access has + * occurred within {@link Config#prefetchDistance} of it. + *

+ * No more data will be prepended to the PagedList before this item. + * + * @param itemAtFront The first item of PagedList + */ + public void onItemAtFrontLoaded(@NonNull T itemAtFront) {} + + /** + * Called when the item at the end of the PagedList has been loaded, and access has + * occurred within {@link Config#prefetchDistance} of it. + *

+ * No more data will be appended to the PagedList after this item. + * + * @param itemAtEnd The first item of PagedList + */ + public void onItemAtEndLoaded(@NonNull T itemAtEnd) {} + } +} diff --git a/paging/src/main/java/com/dbflow5/paging/PageResult.java b/paging/src/main/java/com/dbflow5/paging/PageResult.java new file mode 100644 index 0000000000000000000000000000000000000000..3cf458ec77b40d587fbc64a69d46d34bbf4a8543 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/PageResult.java @@ -0,0 +1,81 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.util.Collections; +import java.util.List; + +class PageResult { + @SuppressWarnings("unchecked") + private static final PageResult EMPTY_RESULT = + new PageResult(Collections.emptyList(), 0); + + @SuppressWarnings("unchecked") + private static final PageResult INVALID_RESULT = + new PageResult(Collections.emptyList(), 0); + + @SuppressWarnings("unchecked") + static PageResult getEmptyResult() { + return EMPTY_RESULT; + } + + @SuppressWarnings("unchecked") + static PageResult getInvalidResult() { + return INVALID_RESULT; + } + + @Retention(SOURCE) + @IntDef({INIT, APPEND, PREPEND, TILE}) + @interface ResultType { + } + + static final int INIT = 0; + + // contiguous results + static final int APPEND = 1; + static final int PREPEND = 2; + + // non-contiguous, tile result + static final int TILE = 3; + + @NonNull + public final List page; + @SuppressWarnings("WeakerAccess") + public final int leadingNulls; + @SuppressWarnings("WeakerAccess") + public final int trailingNulls; + @SuppressWarnings("WeakerAccess") + public final int positionOffset; + + PageResult(@NonNull List list, int leadingNulls, int trailingNulls, int positionOffset) { + this.page = list; + this.leadingNulls = leadingNulls; + this.trailingNulls = trailingNulls; + this.positionOffset = positionOffset; + } + + PageResult(@NonNull List list, int positionOffset) { + this.page = list; + this.leadingNulls = 0; + this.trailingNulls = 0; + this.positionOffset = positionOffset; + } + + @Override + public String toString() { + return "Result " + leadingNulls + + ", " + page + + ", " + trailingNulls + + ", offset " + positionOffset; + } + + public boolean isInvalid() { + return this == INVALID_RESULT; + } + + abstract static class Receiver { + @MainThread + public abstract void onPageResult(@ResultType int type, @NonNull PageResult pageResult); + } +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/PagedStorage.java b/paging/src/main/java/com/dbflow5/paging/PagedStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..9f68266f1e6212ffb302eb536fb8f03a1af9bb5b --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/PagedStorage.java @@ -0,0 +1,631 @@ +package com.dbflow5.paging; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +/** + * Class holding the pages of data backing a PagedList, presenting sparse loaded data as a List. + *

+ * It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds + * data, and does not have any notion of the ideas of async loads, or prefetching. + */ +final class PagedStorage extends AbstractList { + /** + * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item + * in that position is already loading. We use a singleton placeholder list that is distinct + * from Collections.emptyList() for safety. + */ + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private static final List PLACEHOLDER_LIST = new ArrayList(); + + // Always set + private int mLeadingNullCount; + /** + * List of pages in storage. + * + * Two storage modes: + * + * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled(). + * Safe to access any item in any page. + * + * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true. + * mPages may have nulls, or placeholder (empty) pages while content is loading. + */ + private final ArrayList> mPages; + private int mTrailingNullCount; + + private int mPositionOffset; + /** + * Number of loaded items held by {@link #mPages}. When tiling, doesn't count unloaded pages in + * {@link #mPages}. If tiling is disabled, same as {@link #mStorageCount}. + * + * This count is the one used for trimming. + */ + private int mLoadedCount; + + /** + * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in + * {@link #mPages} may be null, but this value still counts them. + */ + private int mStorageCount; + + // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set + private int mPageSize; + + private int mNumberPrepended; + private int mNumberAppended; + + PagedStorage() { + mLeadingNullCount = 0; + mPages = new ArrayList<>(); + mTrailingNullCount = 0; + mPositionOffset = 0; + mLoadedCount = 0; + mStorageCount = 0; + mPageSize = 1; + mNumberPrepended = 0; + mNumberAppended = 0; + } + + PagedStorage(int leadingNulls, List page, int trailingNulls) { + this(); + init(leadingNulls, page, trailingNulls, 0); + } + + private PagedStorage(PagedStorage other) { + mLeadingNullCount = other.mLeadingNullCount; + mPages = new ArrayList<>(other.mPages); + mTrailingNullCount = other.mTrailingNullCount; + mPositionOffset = other.mPositionOffset; + mLoadedCount = other.mLoadedCount; + mStorageCount = other.mStorageCount; + mPageSize = other.mPageSize; + mNumberPrepended = other.mNumberPrepended; + mNumberAppended = other.mNumberAppended; + } + + PagedStorage snapshot() { + return new PagedStorage<>(this); + } + + private void init(int leadingNulls, List page, int trailingNulls, int positionOffset) { + mLeadingNullCount = leadingNulls; + mPages.clear(); + mPages.add(page); + mTrailingNullCount = trailingNulls; + + mPositionOffset = positionOffset; + mLoadedCount = page.size(); + mStorageCount = mLoadedCount; + + // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled + // even if it will break if nulls convert. + mPageSize = page.size(); + + mNumberPrepended = 0; + mNumberAppended = 0; + } + + void init(int leadingNulls, @NonNull List page, int trailingNulls, int positionOffset, + @NonNull Callback callback) { + init(leadingNulls, page, trailingNulls, positionOffset); + callback.onInitialized(size()); + } + + @Override + public T get(int i) { + if (i < 0 || i >= size()) { + throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size()); + } + + // is it definitely outside 'mPages'? + int localIndex = i - mLeadingNullCount; + if (localIndex < 0 || localIndex >= mStorageCount) { + return null; + } + + int localPageIndex; + int pageInternalIndex; + + if (isTiled()) { + // it's inside mPages, and we're tiled. Jump to correct tile. + localPageIndex = localIndex / mPageSize; + pageInternalIndex = localIndex % mPageSize; + } else { + // it's inside mPages, but page sizes aren't regular. Walk to correct tile. + // Pages can only be null while tiled, so accessing page count is safe. + pageInternalIndex = localIndex; + final int localPageCount = mPages.size(); + for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) { + int pageSize = mPages.get(localPageIndex).size(); + if (pageSize > pageInternalIndex) { + // stop, found the page + break; + } + pageInternalIndex -= pageSize; + } + } + + List page = mPages.get(localPageIndex); + if (page == null || page.size() == 0) { + // can only occur in tiled case, with untouched inner/placeholder pages + return null; + } + return page.get(pageInternalIndex); + } + + /** + * Returns true if all pages are the same size, except for the last, which may be smaller + */ + boolean isTiled() { + return mPageSize > 0; + } + + int getLeadingNullCount() { + return mLeadingNullCount; + } + + int getTrailingNullCount() { + return mTrailingNullCount; + } + + int getStorageCount() { + return mStorageCount; + } + + int getNumberAppended() { + return mNumberAppended; + } + + int getNumberPrepended() { + return mNumberPrepended; + } + + int getPageCount() { + return mPages.size(); + } + + int getLoadedCount() { + return mLoadedCount; + } + + interface Callback { + void onInitialized(int count); + void onPagePrepended(int leadingNulls, int changed, int added); + void onPageAppended(int endPosition, int changed, int added); + void onPagePlaceholderInserted(int pageIndex); + void onPageInserted(int start, int count); + void onPagesRemoved(int startOfDrops, int count); + void onPagesSwappedToPlaceholder(int startOfDrops, int count); + void onEmptyPrepend(); + void onEmptyAppend(); + } + + int getPositionOffset() { + return mPositionOffset; + } + + int getMiddleOfLoadedRange() { + return mLeadingNullCount + mPositionOffset + mStorageCount / 2; + } + + @Override + public int size() { + return mLeadingNullCount + mStorageCount + mTrailingNullCount; + } + + int computeLeadingNulls() { + int total = mLeadingNullCount; + final int pageCount = mPages.size(); + for (int i = 0; i < pageCount; i++) { + List page = mPages.get(i); + if (page != null && page != PLACEHOLDER_LIST) { + break; + } + total += mPageSize; + } + return total; + } + + int computeTrailingNulls() { + int total = mTrailingNullCount; + for (int i = mPages.size() - 1; i >= 0; i--) { + List page = mPages.get(i); + if (page != null && page != PLACEHOLDER_LIST) { + break; + } + total += mPageSize; + } + return total; + } + + // ---------------- Trimming API ------------------- + // Trimming is always done at the beginning or end of the list, as content is loaded. + // In addition to trimming pages in the storage, we also support pre-trimming pages (dropping + // them just before they're added) to avoid dispatching an add followed immediately by a trim. + // + // Note - we avoid trimming down to a single page to reduce chances of dropping page in + // viewport, since we don't strictly know the viewport. If trim is aggressively set to size of a + // single page, trimming while the user can see a page boundary is dangerous. To be safe, we + // just avoid trimming in these cases entirely. + + private boolean needsTrim(int maxSize, int requiredRemaining, int localPageIndex) { + List page = mPages.get(localPageIndex); + return page == null || (mLoadedCount > maxSize + && mPages.size() > 2 + && page != PLACEHOLDER_LIST + && mLoadedCount - page.size() >= requiredRemaining); + } + + boolean needsTrimFromFront(int maxSize, int requiredRemaining) { + return needsTrim(maxSize, requiredRemaining, 0); + } + + boolean needsTrimFromEnd(int maxSize, int requiredRemaining) { + return needsTrim(maxSize, requiredRemaining, mPages.size() - 1); + } + + boolean shouldPreTrimNewPage(int maxSize, int requiredRemaining, int countToBeAdded) { + return mLoadedCount + countToBeAdded > maxSize + && mPages.size() > 1 + && mLoadedCount >= requiredRemaining; + } + + boolean trimFromFront(boolean insertNulls, int maxSize, int requiredRemaining, + @NonNull Callback callback) { + int totalRemoved = 0; + while (needsTrimFromFront(maxSize, requiredRemaining)) { + List page = mPages.remove(0); + int removed = (page == null) ? mPageSize : page.size(); + totalRemoved += removed; + mStorageCount -= removed; + mLoadedCount -= (page == null) ? 0 : page.size(); + } + + if (totalRemoved > 0) { + if (insertNulls) { + // replace removed items with nulls + int previousLeadingNulls = mLeadingNullCount; + mLeadingNullCount += totalRemoved; + callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved); + } else { + // simply remove, and handle offset + mPositionOffset += totalRemoved; + callback.onPagesRemoved(mLeadingNullCount, totalRemoved); + } + } + return totalRemoved > 0; + } + + boolean trimFromEnd(boolean insertNulls, int maxSize, int requiredRemaining, + @NonNull Callback callback) { + int totalRemoved = 0; + while (needsTrimFromEnd(maxSize, requiredRemaining)) { + List page = mPages.remove(mPages.size() - 1); + int removed = (page == null) ? mPageSize : page.size(); + totalRemoved += removed; + mStorageCount -= removed; + mLoadedCount -= (page == null) ? 0 : page.size(); + } + + if (totalRemoved > 0) { + int newEndPosition = mLeadingNullCount + mStorageCount; + if (insertNulls) { + // replace removed items with nulls + mTrailingNullCount += totalRemoved; + callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved); + } else { + // items were just removed, signal + callback.onPagesRemoved(newEndPosition, totalRemoved); + } + } + return totalRemoved > 0; + } + + // ---------------- Contiguous API ------------------- + + T getFirstLoadedItem() { + // safe to access first page's first item here: + // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty + return mPages.get(0).get(0); + } + + T getLastLoadedItem() { + // safe to access last page's last item here: + // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty + List page = mPages.get(mPages.size() - 1); + return page.get(page.size() - 1); + } + + void prependPage(@NonNull List page, @NonNull Callback callback) { + final int count = page.size(); + if (count == 0) { + // Nothing returned from source, stop loading in this direction + callback.onEmptyPrepend(); + return; + } + if (mPageSize > 0 && count != mPageSize) { + if (mPages.size() == 1 && count > mPageSize) { + // prepending to a single item - update current page size to that of 'inner' page + mPageSize = count; + } else { + // no longer tiled + mPageSize = -1; + } + } + + mPages.add(0, page); + mLoadedCount += count; + mStorageCount += count; + + final int changedCount = Math.min(mLeadingNullCount, count); + final int addedCount = count - changedCount; + + if (changedCount != 0) { + mLeadingNullCount -= changedCount; + } + mPositionOffset -= addedCount; + mNumberPrepended += count; + + callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount); + } + + void appendPage(@NonNull List page, @NonNull Callback callback) { + final int count = page.size(); + if (count == 0) { + // Nothing returned from source, stop loading in this direction + callback.onEmptyAppend(); + return; + } + + if (mPageSize > 0) { + // if the previous page was smaller than mPageSize, + // or if this page is larger than the previous, disable tiling + if (mPages.get(mPages.size() - 1).size() != mPageSize + || count > mPageSize) { + mPageSize = -1; + } + } + + mPages.add(page); + mLoadedCount += count; + mStorageCount += count; + + final int changedCount = Math.min(mTrailingNullCount, count); + final int addedCount = count - changedCount; + + if (changedCount != 0) { + mTrailingNullCount -= changedCount; + } + mNumberAppended += count; + callback.onPageAppended(mLeadingNullCount + mStorageCount - count, + changedCount, addedCount); + } + + // ------------------ Non-Contiguous API (tiling required) ---------------------- + + /** + * Return true if the page at the passed position would be the first (if trimFromFront) or last + * page that's currently loading. + */ + boolean pageWouldBeBoundary(int positionOfPage, boolean trimFromFront) { + if (mPageSize < 1 || mPages.size() < 2) { + throw new IllegalStateException("Trimming attempt before sufficient load"); + } + + if (positionOfPage < mLeadingNullCount) { + // position represent page in leading nulls + return trimFromFront; + } + + if (positionOfPage >= mLeadingNullCount + mStorageCount) { + // position represent page in trailing nulls + return !trimFromFront; + } + + int localPageIndex = (positionOfPage - mLeadingNullCount) / mPageSize; + + // walk outside in, return false if we find non-placeholder page before localPageIndex + if (trimFromFront) { + for (int i = 0; i < localPageIndex; i++) { + if (mPages.get(i) != null) { + return false; + } + } + } else { + for (int i = mPages.size() - 1; i > localPageIndex; i--) { + if (mPages.get(i) != null) { + return false; + } + } + } + + // didn't find another page, so this one would be a boundary + return true; + } + + void initAndSplit(int leadingNulls, @NonNull List multiPageList, + int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) { + + int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize; + for (int i = 0; i < pageCount; i++) { + int beginInclusive = i * pageSize; + int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize); + + List sublist = multiPageList.subList(beginInclusive, endExclusive); + + if (i == 0) { + // Trailing nulls for first page includes other pages in multiPageList + int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size(); + init(leadingNulls, sublist, initialTrailingNulls, positionOffset); + } else { + int insertPosition = leadingNulls + beginInclusive; + insertPage(insertPosition, sublist, null); + } + } + callback.onInitialized(size()); + } + + void tryInsertPageAndTrim( + int position, + @NonNull List page, + int lastLoad, + int maxSize, + int requiredRemaining, + @NonNull Callback callback) { + boolean trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED; + boolean trimFromFront = lastLoad > getMiddleOfLoadedRange(); + + boolean pageInserted = !trim + || !shouldPreTrimNewPage(maxSize, requiredRemaining, page.size()) + || !pageWouldBeBoundary(position, trimFromFront); + + if (pageInserted) { + insertPage(position, page, callback); + } else { + // trim would have us drop the page we just loaded - swap it to null + int localPageIndex = (position - mLeadingNullCount) / mPageSize; + mPages.set(localPageIndex, null); + + // note: we also remove it, so we don't have to guess how large a 'null' page is later + mStorageCount -= page.size(); + if (trimFromFront) { + mPages.remove(0); + mLeadingNullCount += page.size(); + } else { + mPages.remove(mPages.size() - 1); + mTrailingNullCount += page.size(); + } + } + + if (trim) { + if (trimFromFront) { + trimFromFront(true, maxSize, requiredRemaining, callback); + } else { + trimFromEnd(true, maxSize, requiredRemaining, callback); + } + } + } + + public void insertPage(int position, @NonNull List page, @Nullable Callback callback) { + final int newPageSize = page.size(); + if (newPageSize != mPageSize) { + // differing page size is OK in 2 cases, when the page is being added: + // 1) to the end (in which case, ignore new smaller size) + // 2) only the last page has been added so far (in which case, adopt new bigger size) + + int size = size(); + boolean addingLastPage = position == (size - size % mPageSize) + && newPageSize < mPageSize; + boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1 + && newPageSize > mPageSize; + + // OK only if existing single page, and it's the last one + if (!onlyEndPagePresent && !addingLastPage) { + throw new IllegalArgumentException("page introduces incorrect tiling"); + } + if (onlyEndPagePresent) { + mPageSize = newPageSize; + } + } + + int pageIndex = position / mPageSize; + + allocatePageRange(pageIndex, pageIndex); + + int localPageIndex = pageIndex - mLeadingNullCount / mPageSize; + + List oldPage = mPages.get(localPageIndex); + if (oldPage != null && oldPage != PLACEHOLDER_LIST) { + throw new IllegalArgumentException( + "Invalid position " + position + ": data already loaded"); + } + mPages.set(localPageIndex, page); + mLoadedCount += newPageSize; + if (callback != null) { + callback.onPageInserted(position, newPageSize); + } + } + + void allocatePageRange(final int minimumPage, final int maximumPage) { + int leadingNullPages = mLeadingNullCount / mPageSize; + + if (minimumPage < leadingNullPages) { + for (int i = 0; i < leadingNullPages - minimumPage; i++) { + mPages.add(0, null); + } + int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize; + mStorageCount += newStorageAllocated; + mLeadingNullCount -= newStorageAllocated; + + leadingNullPages = minimumPage; + } + if (maximumPage >= leadingNullPages + mPages.size()) { + int newStorageAllocated = Math.min(mTrailingNullCount, + (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize); + for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) { + mPages.add(mPages.size(), null); + } + mStorageCount += newStorageAllocated; + mTrailingNullCount -= newStorageAllocated; + } + } + + public void allocatePlaceholders(int index, int prefetchDistance, + int pageSize, Callback callback) { + if (pageSize != mPageSize) { + if (pageSize < mPageSize) { + throw new IllegalArgumentException("Page size cannot be reduced"); + } + if (mPages.size() != 1 || mTrailingNullCount != 0) { + // not in single, last page allocated case - can't change page size + throw new IllegalArgumentException( + "Page size can change only if last page is only one present"); + } + mPageSize = pageSize; + } + + final int maxPageCount = (size() + mPageSize - 1) / mPageSize; + int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0); + int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1); + + allocatePageRange(minimumPage, maximumPage); + int leadingNullPages = mLeadingNullCount / mPageSize; + for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) { + int localPageIndex = pageIndex - leadingNullPages; + if (mPages.get(localPageIndex) == null) { + //noinspection unchecked + mPages.set(localPageIndex, PLACEHOLDER_LIST); + callback.onPagePlaceholderInserted(pageIndex); + } + } + } + + public boolean hasPage(int pageSize, int index) { + // NOTE: we pass pageSize here to avoid in case mPageSize + // not fully initialized (when last page only one loaded) + int leadingNullPages = mLeadingNullCount / pageSize; + + if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) { + return false; + } + + List page = mPages.get(index - leadingNullPages); + + return page != null && page != PLACEHOLDER_LIST; + } + + @Override + public String toString() { + StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount + + ", storage " + mStorageCount + + ", trailing " + getTrailingNullCount()); + + for (int i = 0; i < mPages.size(); i++) { + ret.append(" ").append(mPages.get(i)); + } + return ret.toString(); + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/PositionalDataSource.java b/paging/src/main/java/com/dbflow5/paging/PositionalDataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..d5d43c9794201dd04eff79ca14a4fbd98fb577c3 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/PositionalDataSource.java @@ -0,0 +1,530 @@ +package com.dbflow5.paging; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Function; + +public abstract class PositionalDataSource extends DataSource { + + /** + * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}. + */ + @SuppressWarnings("WeakerAccess") + public static class LoadInitialParams { + /** + * Initial load position requested. + *

+ * Note that this may not be within the bounds of your data set, it may need to be adjusted + * before you execute your load. + */ + public final int requestedStartPosition; + + /** + * Requested number of items to load. + *

+ * Note that this may be larger than available data. + */ + public final int requestedLoadSize; + + /** + * Defines page size acceptable for return values. + *

+ * List of items passed to the callback must be an integer multiple of page size. + */ + public final int pageSize; + + /** + * Defines whether placeholders are enabled, and whether the total count passed to + * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored. + */ + public final boolean placeholdersEnabled; + + public LoadInitialParams( + int requestedStartPosition, + int requestedLoadSize, + int pageSize, + boolean placeholdersEnabled) { + this.requestedStartPosition = requestedStartPosition; + this.requestedLoadSize = requestedLoadSize; + this.pageSize = pageSize; + this.placeholdersEnabled = placeholdersEnabled; + } + } + + /** + * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}. + */ + @SuppressWarnings("WeakerAccess") + public static class LoadRangeParams { + /** + * Start position of data to load. + *

+ * Returned data must start at this position. + */ + public final int startPosition; + /** + * Number of items to load. + *

+ * Returned data must be of this size, unless at end of the list. + */ + public final int loadSize; + + public LoadRangeParams(int startPosition, int loadSize) { + this.startPosition = startPosition; + this.loadSize = loadSize; + } + } + + /** + * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} + * to return data, position, and count. + *

+ * A callback should be called only once, and may throw if called again. + *

+ * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). + * + * @param Type of items being loaded. + */ + public abstract static class LoadInitialCallback { + /** + * Called to pass initial load state from a DataSource. + *

+ * Call this method from your DataSource's {@code loadInitial} function to return data, + * and inform how many placeholders should be shown before and after. If counting is cheap + * to compute (for example, if a network load returns the information regardless), it's + * recommended to pass the total size to the totalCount parameter. If placeholders are not + * requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead + * call {@link #onResult(List, int)}. + * + * @param data List of items loaded from the DataSource. If this is empty, the DataSource + * is treated as empty, and no further loads will occur. + * @param position Position of the item at the front of the list. If there are {@code N} + * items before the items in data that can be loaded from this DataSource, + * pass {@code N}. + * @param totalCount Total number of items that may be returned from this DataSource. + * Includes the number in the initial {@code data} parameter + * as well as any items that can be loaded in front or behind of + * {@code data}. + */ + public abstract void onResult(@NonNull List data, int position, int totalCount); + + /** + * Called to pass initial load state from a DataSource without total count, + * when placeholders aren't requested. + *

Note: This method can only be called when placeholders + * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false). + *

+ * Call this method from your DataSource's {@code loadInitial} function to return data, + * if position is known but total size is not. If placeholders are requested, call the three + * parameter variant: {@link #onResult(List, int, int)}. + * + * @param data List of items loaded from the DataSource. If this is empty, the DataSource + * is treated as empty, and no further loads will occur. + * @param position Position of the item at the front of the list. If there are {@code N} + * items before the items in data that can be provided by this DataSource, + * pass {@code N}. + */ + public abstract void onResult(@NonNull List data, int position); + } + + /** + * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)} + * to return data. + *

+ * A callback should be called only once, and may throw if called again. + *

+ * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). + * + * @param Type of items being loaded. + */ + public abstract static class LoadRangeCallback { + /** + * Called to pass loaded data from {@link #loadRange(LoadRangeParams, LoadRangeCallback)}. + * + * @param data List of items loaded from the DataSource. Must be same size as requested, + * unless at end of list. + */ + public abstract void onResult(@NonNull List data); + } + + static class LoadInitialCallbackImpl extends LoadInitialCallback { + final LoadCallbackHelper mCallbackHelper; + private final boolean mCountingEnabled; + private final int mPageSize; + + LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled, + int pageSize, PageResult.Receiver receiver) { + mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver); + mCountingEnabled = countingEnabled; + mPageSize = pageSize; + if (mPageSize < 1) { + throw new IllegalArgumentException("Page size must be non-negative"); + } + } + + @Override + public void onResult(@NonNull List data, int position, int totalCount) { + if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { + LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount); + if (position + data.size() != totalCount + && data.size() % mPageSize != 0) { + throw new IllegalArgumentException("PositionalDataSource requires initial load" + + " size to be a multiple of page size to support internal tiling." + + " loadSize " + data.size() + ", position " + position + + ", totalCount " + totalCount + ", pageSize " + mPageSize); + } + + if (mCountingEnabled) { + int trailingUnloadedCount = totalCount - position - data.size(); + mCallbackHelper.dispatchResultToReceiver( + new PageResult<>(data, position, trailingUnloadedCount, 0)); + } else { + // Only occurs when wrapped as contiguous + mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position)); + } + } + } + + @Override + public void onResult(@NonNull List data, int position) { + if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { + if (position < 0) { + throw new IllegalArgumentException("Position must be non-negative"); + } + if (data.isEmpty() && position != 0) { + throw new IllegalArgumentException( + "Initial result cannot be empty if items are present in data set."); + } + if (mCountingEnabled) { + throw new IllegalStateException("Placeholders requested, but totalCount not" + + " provided. Please call the three-parameter onResult method, or" + + " disable placeholders in the PagedList.Config"); + } + mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position)); + } + } + } + + static class LoadRangeCallbackImpl extends LoadRangeCallback { + private LoadCallbackHelper mCallbackHelper; + private final int mPositionOffset; + + LoadRangeCallbackImpl(@NonNull PositionalDataSource dataSource, + @PageResult.ResultType int resultType, int positionOffset, + Executor mainThreadExecutor, PageResult.Receiver receiver) { + mCallbackHelper = new LoadCallbackHelper<>( + dataSource, resultType, mainThreadExecutor, receiver); + mPositionOffset = positionOffset; + } + + @Override + public void onResult(@NonNull List data) { + if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { + mCallbackHelper.dispatchResultToReceiver(new PageResult<>( + data, 0, 0, mPositionOffset)); + } + } + } + + final void dispatchLoadInitial(boolean acceptCount, + int requestedStartPosition, int requestedLoadSize, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver receiver) { + LoadInitialCallbackImpl callback = + new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver); + + LoadInitialParams params = new LoadInitialParams( + requestedStartPosition, requestedLoadSize, pageSize, acceptCount); + loadInitial(params, callback); + + // If initialLoad's callback is not called within the body, we force any following calls + // to post to the UI thread. This constructor may be run on a background thread, but + // after constructor, mutation must happen on UI thread. + callback.mCallbackHelper.setPostExecutor(mainThreadExecutor); + } + + final void dispatchLoadRange(@PageResult.ResultType int resultType, int startPosition, + int count, @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver) { + LoadRangeCallback callback = new LoadRangeCallbackImpl<>( + this, resultType, startPosition, mainThreadExecutor, receiver); + if (count == 0) { + callback.onResult(Collections.emptyList()); + } else { + loadRange(new LoadRangeParams(startPosition, count), callback); + } + } + + /** + * Load initial list data. + *

+ * This method is called to load the initial page(s) from the DataSource. + *

+ * Result list must be a multiple of pageSize to enable efficient tiling. + * + * @param params Parameters for initial load, including requested start position, load size, and + * page size. + * @param callback Callback that receives initial load data, including + * position and total data set size. + */ + @WorkerThread + public abstract void loadInitial( + @NonNull LoadInitialParams params, + @NonNull LoadInitialCallback callback); + + /** + * Called to load a range of data from the DataSource. + *

+ * This method is called to load additional pages from the DataSource after the + * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList. + *

+ * Unlike {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, this method must return + * the number of items requested, at the position requested. + * + * @param params Parameters for load, including start position and load size. + * @param callback Callback that receives loaded data. + */ + @WorkerThread + public abstract void loadRange(@NonNull LoadRangeParams params, + @NonNull LoadRangeCallback callback); + + @Override + boolean isContiguous() { + return false; + } + + @NonNull + ContiguousDataSource wrapAsContiguousWithoutPlaceholders() { + return new ContiguousWithoutPlaceholdersWrapper<>(this); + } + + /** + * Helper for computing an initial position in + * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be + * computed ahead of loading. + *

+ * The value computed by this function will do bounds checking, page alignment, and positioning + * based on initial load size requested. + *

+ * Example usage in a PositionalDataSource subclass: + *

+     * class ItemDataSource extends PositionalDataSource<Item> {
+     *     private int computeCount() {
+     *         // actual count code here
+     *     }
+     *
+     *     private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+     *         // actual load code here
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
+     *             {@literal @}NonNull LoadInitialCallback<Item> callback) {
+     *         int totalCount = computeCount();
+     *         int position = computeInitialLoadPosition(params, totalCount);
+     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
+     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
+     *             {@literal @}NonNull LoadRangeCallback<Item> callback) {
+     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+     *     }
+     * }
+ * + * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, + * including page size, and requested start/loadSize. + * @param totalCount Total size of the data set. + * @return Position to start loading at. + * @see #computeInitialLoadSize(LoadInitialParams, int, int) + */ + public static int computeInitialLoadPosition(@NonNull LoadInitialParams params, + int totalCount) { + int position = params.requestedStartPosition; + int initialLoadSize = params.requestedLoadSize; + int pageSize = params.pageSize; + + int pageStart = position / pageSize * pageSize; + + // maximum start pos is that which will encompass end of list + int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize; + pageStart = Math.min(maximumLoadPage, pageStart); + + // minimum start position is 0 + pageStart = Math.max(0, pageStart); + + return pageStart; + } + + /** + * Helper for computing an initial load size in + * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be + * computed ahead of loading. + *

+ * This function takes the requested load size, and bounds checks it against the value returned + * by {@link #computeInitialLoadPosition(LoadInitialParams, int)}. + *

+ * Example usage in a PositionalDataSource subclass: + *

+     * class ItemDataSource extends PositionalDataSource<Item> {
+     *     private int computeCount() {
+     *         // actual count code here
+     *     }
+     *
+     *     private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+     *         // actual load code here
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
+     *             {@literal @}NonNull LoadInitialCallback<Item> callback) {
+     *         int totalCount = computeCount();
+     *         int position = computeInitialLoadPosition(params, totalCount);
+     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
+     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
+     *             {@literal @}NonNull LoadRangeCallback<Item> callback) {
+     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+     *     }
+     * }
+ * + * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, + * including page size, and requested start/loadSize. + * @param initialLoadPosition Value returned by + * {@link #computeInitialLoadPosition(LoadInitialParams, int)} + * @param totalCount Total size of the data set. + * @return Number of items to load. + * @see #computeInitialLoadPosition(LoadInitialParams, int) + */ + @SuppressWarnings("WeakerAccess") + public static int computeInitialLoadSize(@NonNull LoadInitialParams params, + int initialLoadPosition, int totalCount) { + return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize); + } + + @SuppressWarnings("deprecation") + static class ContiguousWithoutPlaceholdersWrapper + extends ContiguousDataSource { + @NonNull + final PositionalDataSource mSource; + + ContiguousWithoutPlaceholdersWrapper( + @NonNull PositionalDataSource source) { + mSource = source; + } + + @Override + public void addInvalidatedCallback( + @NonNull InvalidatedCallback onInvalidatedCallback) { + mSource.addInvalidatedCallback(onInvalidatedCallback); + } + + @Override + public void removeInvalidatedCallback( + @NonNull InvalidatedCallback onInvalidatedCallback) { + mSource.removeInvalidatedCallback(onInvalidatedCallback); + } + + @Override + public void invalidate() { + mSource.invalidate(); + } + + @Override + public boolean isInvalid() { + return mSource.isInvalid(); + } + + @NonNull + @Override + public DataSource mapByPage( + @NonNull Function, List> function) { + throw new UnsupportedOperationException( + "Inaccessible inner type doesn't support map op"); + } + + @NonNull + @Override + public DataSource map( + @NonNull Function function) { + throw new UnsupportedOperationException( + "Inaccessible inner type doesn't support map op"); + } + + @Override + void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize, + boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver) { + + if (position == null) { + position = 0; + } else { + // snap load size to page multiple (minimum two) + initialLoadSize = (Math.max(initialLoadSize / pageSize, 2)) * pageSize; + + // move start pos so that the load is centered around the key, not starting at it + final int idealStart = position - initialLoadSize / 2; + position = Math.max(0, idealStart / pageSize * pageSize); + } + + // Note enablePlaceholders will be false here, but we don't have a way to communicate + // this to PositionalDataSource. This is fine, because only the list and its position + // offset will be consumed by the LoadInitialCallback. + mSource.dispatchLoadInitial(false, position, initialLoadSize, + pageSize, mainThreadExecutor, receiver); + } + + @Override + void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver) { + int startIndex = currentEndIndex + 1; + mSource.dispatchLoadRange( + PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver); + } + + @Override + void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, + int pageSize, @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver receiver) { + int startIndex = currentBeginIndex - 1; + if (startIndex < 0) { + // trigger empty list load + mSource.dispatchLoadRange( + PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver); + } else { + int loadSize = Math.min(pageSize, startIndex + 1); + startIndex = startIndex - loadSize + 1; + mSource.dispatchLoadRange( + PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver); + } + } + + @Override + Integer getKey(int position, Value item) { + return position; + } + + } + + @NonNull + @Override + public final PositionalDataSource mapByPage( + @NonNull Function, List> function) { + return new WrapperPositionalDataSource<>(this, function); + } + + @NonNull + @Override + public final PositionalDataSource map(@NonNull Function function) { + return mapByPage(createListFunction(function)); + } +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/QueryDataSource.java b/paging/src/main/java/com/dbflow5/paging/QueryDataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..05d02328380bbc20172701abb35ad4832a25e100 --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/QueryDataSource.java @@ -0,0 +1,111 @@ +package com.dbflow5.paging; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.observing.OnTableChangedObserver; +import com.dbflow5.observing.TableObserver; +import com.dbflow5.query.*; +import com.dbflow5.transaction.Transaction; + +import java.util.*; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class QueryDataSource & ModelQueriable> extends PositionalDataSource { + private final TQuery transformable; + private final DBFlowDatabase database; + + public QueryDataSource(TQuery transformable, DBFlowDatabase database) { + this.transformable = transformable; + this.database = database; + + if (transformable instanceof WhereBase && !(((WhereBase) transformable).queryBuilderBase() instanceof Select)) { + throw new IllegalArgumentException("Cannot pass a non-SELECT cursor into this data source."); + } + + DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables().stream().findFirst().get()); + // force initialize the db + db.getWritableDatabase(); + + TableObserver observer = db.tableObserver(); + // From could be part of many joins, so we register for all affected tables here. + observer.addOnTableChangedObserver(onTableChangedObserver); + } + + private Set> associatedTables() { + Set> classSet = null; + From from = transformable.extractFrom(); + if(from != null){ + classSet = from.associatedTables(); + } + if(classSet == null){ + classSet = new HashSet>(Collections.singleton(transformable.table())); + } + + return classSet; + } + + private final OnTableChangedObserver onTableChangedObserver = new OnTableChangedObserver(new ArrayList<>(associatedTables())) { + @Override + protected void onChanged(Set> tables) { + if (!tables.isEmpty()) { + invalidate(); + } + } + }; + + @Override + public void loadRange(LoadRangeParams params, LoadRangeCallback callback) { + database.beginTransactionAsync((Function>) db -> transformable.constrain(params.startPosition, params.loadSize).queryList(db)).execute(null, null, null, (listTransaction, ts) -> { + callback.onResult(ts); + return null; + }); + } + + @Override + public void loadInitial(LoadInitialParams params, LoadInitialCallback callback) { + database.beginTransactionAsync((Function) db -> SQLite.selectCountOf().from(transformable).longValue(db)).execute(null, null, null, new BiFunction, Long, Void>() { + @Override + public Void apply(Transaction objectTransaction, Long count) { + final int max; + if(params.requestedLoadSize >= count - 1) { + max = count.intValue(); + }else { + max = params.requestedLoadSize; + } + database.beginTransactionAsync((Function>) db -> transformable.constrain(params.requestedStartPosition, max).queryList(db)).execute(null, null, null, (listTransaction, ts) -> { + callback.onResult(ts, params.requestedStartPosition, count.intValue()); + return null; + }); + return null; + } + }); + + } + + static class Factory & ModelQueriable> extends DataSource.Factory { + private final TQuery transformable; + private final DBFlowDatabase database; + + public Factory(TQuery transformable, DBFlowDatabase database) { + this.transformable = transformable; + this.database = database; + } + + @Override + public DataSource create() { + return new QueryDataSource<>(transformable, database); + } + } + + public static & ModelQueriable> Factory newFactory(TQuery transformable, DBFlowDatabase database) { + return new Factory<>(transformable, database); + } + + public static & ModelQueriable> Factory toDataSourceFactory(TQuery query, DBFlowDatabase database) { + return newFactory(query, database); + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/RestrictTo.java b/paging/src/main/java/com/dbflow5/paging/RestrictTo.java new file mode 100644 index 0000000000000000000000000000000000000000..df4fc4138e3af8b28cafdd3ec2139b2eab7ef0af --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/RestrictTo.java @@ -0,0 +1,73 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(CLASS) +@Target({ANNOTATION_TYPE,TYPE,METHOD,CONSTRUCTOR,FIELD,PACKAGE}) +public @interface RestrictTo { + + /** + * The scope to which usage should be restricted. + */ + Scope[] value(); + + enum Scope { + /** + * Restrict usage to code within the same library (e.g. the same + * gradle group ID and artifact ID). + */ + LIBRARY, + + /** + * Restrict usage to code within the same group of libraries. + * This corresponds to the gradle group ID. + */ + LIBRARY_GROUP, + + /** + * Restrict usage to code within packages whose groups share + * the same library group prefix up to the last ".", so for + * example libraries foo.bar:lib1 amd foo.baz:lib2 share + * the prefix "foo." and so they can use each other's + * apis that are restricted to this scope. Similarly for + * com.foo.bar:lib1 and com.foo.baz:lib2 where they share + * "com.foo.". Library com.bar.qux:lib3 however will not + * be able to use the restricted api because it only + * shares the prefix "com." and not all the way until the + * last ".". + */ + LIBRARY_GROUP_PREFIX, + + /** + * Restrict usage to code within the same group ID (based on gradle + * group ID). This is an alias for {@link #LIBRARY_GROUP_PREFIX}. + * + * @deprecated Use {@link #LIBRARY_GROUP_PREFIX} instead + */ + @Deprecated + GROUP_ID, + + /** + * Restrict usage to tests. + */ + TESTS, + + /** + * Restrict usage to subclasses of the enclosing class. + *

+ * Note: This scope should not be used to annotate + * packages. + */ + SUBCLASSES, + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/SnapshotPagedList.java b/paging/src/main/java/com/dbflow5/paging/SnapshotPagedList.java new file mode 100644 index 0000000000000000000000000000000000000000..e6dd7c3a1e9bea51a5f548139749a8f49ea4039c --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/SnapshotPagedList.java @@ -0,0 +1,56 @@ +package com.dbflow5.paging; + +class SnapshotPagedList extends PagedList { + private final boolean mContiguous; + private final Object mLastKey; + private final DataSource mDataSource; + + SnapshotPagedList(@NonNull PagedList pagedList) { + super(pagedList.mStorage.snapshot(), + pagedList.mMainThreadExecutor, + pagedList.mBackgroundThreadExecutor, + null, + pagedList.mConfig); + mDataSource = pagedList.getDataSource(); + mContiguous = pagedList.isContiguous(); + mLastLoad = pagedList.mLastLoad; + mLastKey = pagedList.getLastKey(); + } + + @Override + public boolean isImmutable() { + return true; + } + + @Override + public boolean isDetached() { + return true; + } + + @Override + boolean isContiguous() { + return mContiguous; + } + + @Nullable + @Override + public Object getLastKey() { + return mLastKey; + } + + @NonNull + @Override + public DataSource getDataSource() { + return mDataSource; + } + + @Override + void dispatchUpdatesSinceSnapshot(@NonNull PagedList storageSnapshot, + @NonNull Callback callback) { + } + + @Override + void loadAroundInternal(int index) { + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/TiledPagedList.java b/paging/src/main/java/com/dbflow5/paging/TiledPagedList.java new file mode 100644 index 0000000000000000000000000000000000000000..76eb7170f327fd0f1c3258e056b6de2b31b2983d --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/TiledPagedList.java @@ -0,0 +1,210 @@ +package com.dbflow5.paging; + +import java.util.List; +import java.util.concurrent.Executor; + +class TiledPagedList extends PagedList + implements PagedStorage.Callback { + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final PositionalDataSource mDataSource; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + PageResult.Receiver mReceiver = new PageResult.Receiver() { + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread + @Override + public void onPageResult(@PageResult.ResultType int type, + @NonNull PageResult pageResult) { + if (pageResult.isInvalid()) { + detach(); + return; + } + + if (isDetached()) { + // No op, have detached + return; + } + + if (type != PageResult.INIT && type != PageResult.TILE) { + throw new IllegalArgumentException("unexpected resultType" + type); + } + + List page = pageResult.page; + if (mStorage.getPageCount() == 0) { + mStorage.initAndSplit( + pageResult.leadingNulls, page, pageResult.trailingNulls, + pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this); + } else { + mStorage.tryInsertPageAndTrim( + pageResult.positionOffset, + page, + mLastLoad, + mConfig.maxSize, + mRequiredRemainder, + TiledPagedList.this); + } + + if (mBoundaryCallback != null) { + boolean deferEmpty = mStorage.size() == 0; + boolean deferBegin = !deferEmpty + && pageResult.leadingNulls == 0 + && pageResult.positionOffset == 0; + int size = size(); + boolean deferEnd = !deferEmpty + && ((type == PageResult.INIT && pageResult.trailingNulls == 0) + || (type == PageResult.TILE + && (pageResult.positionOffset + mConfig.pageSize >= size))); + deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); + } + } + }; + + @WorkerThread + TiledPagedList(@NonNull PositionalDataSource dataSource, + @NonNull Executor mainThreadExecutor, + @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback boundaryCallback, + @NonNull Config config, + int position) { + super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config); + mDataSource = dataSource; + + final int pageSize = mConfig.pageSize; + mLastLoad = position; + + if (mDataSource.isInvalid()) { + detach(); + } else { + final int firstLoadSize = + (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize; + + final int idealStart = position - firstLoadSize / 2; + final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize); + + mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize, + pageSize, mMainThreadExecutor, mReceiver); + } + } + + @Override + boolean isContiguous() { + return false; + } + + @NonNull + @Override + public DataSource getDataSource() { + return mDataSource; + } + + @Nullable + @Override + public Object getLastKey() { + return mLastLoad; + } + + @Override + protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList pagedListSnapshot, + @NonNull Callback callback) { + //noinspection UnnecessaryLocalVariable + final PagedStorage snapshot = pagedListSnapshot.mStorage; + + if (snapshot.isEmpty() + || mStorage.size() != snapshot.size()) { + throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" + + " to be a snapshot of this PagedList"); + } + + // loop through each page and signal the callback for any pages that are present now, + // but not in the snapshot. + final int pageSize = mConfig.pageSize; + final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize; + final int pageCount = mStorage.getPageCount(); + for (int i = 0; i < pageCount; i++) { + int pageIndex = i + leadingNullPages; + int updatedPages = 0; + // count number of consecutive pages that were added since the snapshot... + while (updatedPages < mStorage.getPageCount() + && mStorage.hasPage(pageSize, pageIndex + updatedPages) + && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) { + updatedPages++; + } + // and signal them all at once to the callback + if (updatedPages > 0) { + callback.onChanged(pageIndex * pageSize, pageSize * updatedPages); + i += updatedPages - 1; + } + } + } + + @Override + protected void loadAroundInternal(int index) { + mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this); + } + + @Override + public void onInitialized(int count) { + notifyInserted(0, count); + } + + @Override + public void onPagePrepended(int leadingNulls, int changed, int added) { + throw new IllegalStateException("Contiguous callback on TiledPagedList"); + } + + @Override + public void onPageAppended(int endPosition, int changed, int added) { + throw new IllegalStateException("Contiguous callback on TiledPagedList"); + } + + @Override + public void onEmptyPrepend() { + throw new IllegalStateException("Contiguous callback on TiledPagedList"); + } + + @Override + public void onEmptyAppend() { + throw new IllegalStateException("Contiguous callback on TiledPagedList"); + } + + @Override + public void onPagePlaceholderInserted(final int pageIndex) { + // placeholder means initialize a load + mBackgroundThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (isDetached()) { + return; + } + final int pageSize = mConfig.pageSize; + + if (mDataSource.isInvalid()) { + detach(); + } else { + int startPosition = pageIndex * pageSize; + int count = Math.min(pageSize, mStorage.size() - startPosition); + mDataSource.dispatchLoadRange( + PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver); + } + } + }); + } + + @Override + public void onPageInserted(int start, int count) { + notifyChanged(start, count); + } + + @Override + public void onPagesRemoved(int startOfDrops, int count) { + notifyRemoved(startOfDrops, count); + } + + @Override + public void onPagesSwappedToPlaceholder(int startOfDrops, int count) { + notifyChanged(startOfDrops, count); + } +} + diff --git a/paging/src/main/java/com/dbflow5/paging/WorkerThread.java b/paging/src/main/java/com/dbflow5/paging/WorkerThread.java new file mode 100644 index 0000000000000000000000000000000000000000..05d27236c2bafb02514d35a3c9cd92d88e7ebbaa --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/WorkerThread.java @@ -0,0 +1,17 @@ +package com.dbflow5.paging; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface WorkerThread { +} \ No newline at end of file diff --git a/paging/src/main/java/com/dbflow5/paging/WrapperPositionalDataSource.java b/paging/src/main/java/com/dbflow5/paging/WrapperPositionalDataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..0df8f4d18f62783ae50ff37a2e8808996253b35f --- /dev/null +++ b/paging/src/main/java/com/dbflow5/paging/WrapperPositionalDataSource.java @@ -0,0 +1,63 @@ +package com.dbflow5.paging; + +import java.util.List; +import java.util.function.Function; + +class WrapperPositionalDataSource extends PositionalDataSource { + private final PositionalDataSource mSource; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final Function, List> mListFunction; + + WrapperPositionalDataSource(PositionalDataSource source, + Function, List> listFunction) { + mSource = source; + mListFunction = listFunction; + } + + @Override + public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + mSource.addInvalidatedCallback(onInvalidatedCallback); + } + + @Override + public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + mSource.removeInvalidatedCallback(onInvalidatedCallback); + } + + @Override + public void invalidate() { + mSource.invalidate(); + } + + @Override + public boolean isInvalid() { + return mSource.isInvalid(); + } + + @Override + public void loadInitial(@NonNull LoadInitialParams params, + final @NonNull LoadInitialCallback callback) { + mSource.loadInitial(params, new LoadInitialCallback() { + @Override + public void onResult(@NonNull List data, int position, int totalCount) { + callback.onResult(convert(mListFunction, data), position, totalCount); + } + + @Override + public void onResult(@NonNull List data, int position) { + callback.onResult(convert(mListFunction, data), position); + } + }); + } + + @Override + public void loadRange(@NonNull LoadRangeParams params, + final @NonNull LoadRangeCallback callback) { + mSource.loadRange(params, new LoadRangeCallback() { + @Override + public void onResult(@NonNull List data) { + callback.onResult(convert(mListFunction, data)); + } + }); + } +} diff --git a/paging/src/main/kotlin/com/dbflow5/paging/QueryDataSource.kt b/paging/src/main/kotlin/com/dbflow5/paging/QueryDataSource.kt deleted file mode 100644 index 3dffb54fa85c515f8fa9d233857cd5e0d07d76c6..0000000000000000000000000000000000000000 --- a/paging/src/main/kotlin/com/dbflow5/paging/QueryDataSource.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.dbflow5.paging - -import androidx.paging.DataSource -import androidx.paging.PositionalDataSource -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowManager -import com.dbflow5.observing.OnTableChangedObserver -import com.dbflow5.query.ModelQueriable -import com.dbflow5.query.Select -import com.dbflow5.query.Transformable -import com.dbflow5.query.WhereBase -import com.dbflow5.query.extractFrom -import com.dbflow5.query.selectCountOf - -/** - * Bridges the [ModelQueriable] into a [PositionalDataSource] that loads a [ModelQueriable]. - */ -class QueryDataSource -internal constructor(private val transformable: TQuery, - private val database: DBFlowDatabase) - : PositionalDataSource() where TQuery : Transformable, TQuery : ModelQueriable { - - private val associatedTables: Set> = transformable.extractFrom()?.associatedTables - ?: setOf(transformable.table) - - private val onTableChangedObserver = object : OnTableChangedObserver(associatedTables.toList()) { - override fun onChanged(tables: Set>) { - if (tables.isNotEmpty()) { - invalidate() - } - } - } - - init { - if (transformable is WhereBase<*> && transformable.queryBuilderBase !is Select) { - throw IllegalArgumentException("Cannot pass a non-SELECT cursor into this data source.") - } - - val db = FlowManager.getDatabaseForTable(associatedTables.first()) - // force initialize the db - db.writableDatabase - - val observer = db.tableObserver - // From could be part of many joins, so we register for all affected tables here. - observer.addOnTableChangedObserver(onTableChangedObserver) - - } - - override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) { - database.beginTransactionAsync { db -> - transformable.constrain(params.startPosition.toLong(), params.loadSize.toLong()) - .queryList(db) - }.execute { _, list -> callback.onResult(list) } - } - - override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { - database.beginTransactionAsync { db -> selectCountOf().from(transformable).longValue(db) } - .execute { _, count -> - val max = when { - params.requestedLoadSize >= count - 1 -> count.toInt() - else -> params.requestedLoadSize - } - database.beginTransactionAsync { db -> - transformable.constrain(params.requestedStartPosition.toLong(), max.toLong()).queryList(db) - }.execute { _, list -> - callback.onResult(list, params.requestedStartPosition, count.toInt()) - } - } - } - - class Factory - internal constructor(private val transformable: TQuery, - private val database: DBFlowDatabase) - : DataSource.Factory() where TQuery : Transformable, TQuery : ModelQueriable { - override fun create(): DataSource = QueryDataSource(transformable, database) - } - - companion object { - @JvmStatic - fun newFactory(transformable: TQuery, database: DBFlowDatabase) - where TQuery : Transformable, TQuery : ModelQueriable = - Factory(transformable, database) - } -} - -fun TQuery.toDataSourceFactory(database: DBFlowDatabase) - where TQuery : Transformable, TQuery : ModelQueriable = - QueryDataSource.newFactory(this, database) \ No newline at end of file diff --git a/paging/src/main/resources/base/element/string.json b/paging/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..ed025d9dec2f9def12d88a3ca5aa52125b7f9ca3 --- /dev/null +++ b/paging/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "paging_library", + "value": "paging_library" + } + ] +} diff --git a/paging/src/test/java/com/dbflow5/paging/ExampleTest.java b/paging/src/test/java/com/dbflow5/paging/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cbca14eac25f81852229a21b67dde56cf9ba1e53 --- /dev/null +++ b/paging/src/test/java/com/dbflow5/paging/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.paging; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/processor/.gitignore b/processor/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/processor/.gitignore @@ -0,0 +1 @@ +/build diff --git a/processor/build.gradle b/processor/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..9871ba4cae09f443b86ac56582bef04397ea8dfc --- /dev/null +++ b/processor/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'java-library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + compileOnly('org.glassfish:javax.annotation:10.0-b28') + compile 'com.squareup:javapoet:1.11.1' + api project(path: ':core') + api project(path: ':contentprovider-annotations') + + api 'org.jetbrains:annotations:13.0' + + compile 'com.google.auto.service:auto-service:1.0-rc3' + annotationProcessor 'com.google.auto.service:auto-service:1.0-rc3' + +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" diff --git a/processor/build.gradle.kts b/processor/build.gradle.kts deleted file mode 100644 index 5343edb2f4af2bf49ecb51c30f3818bd87862c1d..0000000000000000000000000000000000000000 --- a/processor/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") -} - -// project.ext.artifactId = bt_name - -tasks.withType { - kotlinOptions.jvmTarget = "1.8" -} - -dependencies { - api(project(":core")) - api(project(":contentprovider-annotations")) - api(Dependencies.JavaPoet) - api(Dependencies.KPoet) - - compileOnly(Dependencies.JavaXAnnotation) - testImplementation(Dependencies.JUnit) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/processor/gradle.properties b/processor/gradle.properties deleted file mode 100644 index 24cb0205c23833e303777b03e987a0ed6e78c3d5..0000000000000000000000000000000000000000 --- a/processor/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-processor -bt_packaging=aar -bt_artifact_id=dbflow-processor \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/ClassNames.java b/processor/src/main/java/com/dbflow5/processor/ClassNames.java new file mode 100644 index 0000000000000000000000000000000000000000..87b17d7ed2f40b1b317cc2dd56da470982453b0c --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/ClassNames.java @@ -0,0 +1,98 @@ +package com.dbflow5.processor; + +import com.dbflow5.annotation.ConflictAction; +import com.squareup.javapoet.ClassName; + +/** + * Description: The static FQCN string file to assist in providing class names for imports and more in the Compiler + */ +public class ClassNames { + public static final String BASE_PACKAGE = "com.dbflow5"; + public static final String FLOW_MANAGER_PACKAGE = BASE_PACKAGE + ".config"; + public static final String DATABASE_HOLDER_STATIC_CLASS_NAME = "GeneratedDatabaseHolder"; + public static final String CONVERTER = BASE_PACKAGE + ".converter"; + public static final String ADAPTER = BASE_PACKAGE + ".adapter"; + public static final String QUERY_PACKAGE = BASE_PACKAGE + ".query"; + public static final String STRUCTURE = BASE_PACKAGE + ".structure"; + public static final String DATABASE = BASE_PACKAGE + ".database"; + public static final String QUERIABLE = ADAPTER + ".queriable"; + public static final String PROPERTY_PACKAGE = QUERY_PACKAGE + ".property"; + public static final String CONFIG = BASE_PACKAGE + ".config"; + public static final String RUNTIME = BASE_PACKAGE + ".runtime"; + public static final String SAVEABLE = ADAPTER + ".saveable"; + public static final String PROVIDER = BASE_PACKAGE + ".provider"; + + public static final ClassName DATABASE_HOLDER = ClassName.get(CONFIG, "DatabaseHolder"); + public static final ClassName FLOW_MANAGER = ClassName.get(CONFIG, "FlowManager"); + public static final ClassName BASE_DATABASE_DEFINITION_CLASSNAME = ClassName.get(CONFIG, "DBFlowDatabase"); + public static final ClassName CONTENT_PROVIDER_DATABASE = ClassName.get(PROVIDER, "ContentProviderDatabase"); + + public static final ClassName URI = ClassName.get("android.net", "Uri"); + public static final ClassName URI_MATCHER = ClassName.get("android.content", "UriMatcher"); + public static final ClassName CURSOR = ClassName.get("android.database", "Cursor"); + public static final ClassName FLOW_CURSOR = ClassName.get(DATABASE, "FlowCursor"); + public static final ClassName DATABASE_UTILS = ClassName.get("android.database", "DatabaseUtils"); + public static final ClassName CONTENT_VALUES = ClassName.get("android.content", "ContentValues"); + public static final ClassName CONTENT_URIS = ClassName.get("android.content", "ContentUris"); + + public static final ClassName MODEL_ADAPTER = ClassName.get(ADAPTER, "ModelAdapter"); + public static final ClassName RETRIEVAL_ADAPTER = ClassName.get(ADAPTER, "RetrievalAdapter"); + public static final ClassName MODEL = ClassName.get(STRUCTURE, "Model"); + public static final ClassName MODEL_VIEW_ADAPTER = ClassName.get(ADAPTER, "ModelViewAdapter"); + public static final ClassName OBJECT_TYPE = ClassName.get(ADAPTER, "ObjectType"); + + public static final ClassName DATABASE_STATEMENT = ClassName.get(DATABASE, "DatabaseStatement"); + + public static final ClassName QUERY = ClassName.get(QUERY_PACKAGE, "Query"); + + public static final ClassName TYPE_CONVERTER = ClassName.get(CONVERTER, "TypeConverter"); + public static final ClassName TYPE_CONVERTER_GETTER = ClassName.get(PROPERTY_PACKAGE, + "TypeConvertedProperty.TypeConverterGetter"); + + public static final ClassName CONFLICT_ACTION = ClassName.get(ConflictAction.class); + + public static final ClassName CONTENT_VALUES_LISTENER = ClassName.get(QUERY_PACKAGE, "ContentValuesListener"); + public static final ClassName LOAD_FROM_CURSOR_LISTENER = ClassName.get(QUERY_PACKAGE, "LoadFromCursorListener"); + public static final ClassName SQLITE_STATEMENT_LISTENER = ClassName.get(QUERY_PACKAGE, "SQLiteStatementListener"); + + + public static final ClassName PROPERTY = ClassName.get(PROPERTY_PACKAGE, "Property"); + public static final ClassName TYPE_CONVERTED_PROPERTY = ClassName.get(PROPERTY_PACKAGE, "TypeConvertedProperty"); + public static final ClassName WRAPPER_PROPERTY = ClassName.get(PROPERTY_PACKAGE, "WrapperProperty"); + + public static final ClassName IPROPERTY = ClassName.get(PROPERTY_PACKAGE, "IProperty"); + public static final ClassName INDEX_PROPERTY = ClassName.get(PROPERTY_PACKAGE, "IndexProperty"); + public static final ClassName OPERATOR_GROUP = ClassName.get(QUERY_PACKAGE, "OperatorGroup"); + + public static final ClassName ICONDITIONAL = ClassName.get(QUERY_PACKAGE, "IConditional"); + + public static final ClassName BASE_CONTENT_PROVIDER = ClassName.get(PROVIDER, "BaseContentProvider"); + + public static final ClassName BASE_MODEL = ClassName.get(STRUCTURE, "BaseModel"); + public static final ClassName MODEL_CACHE = ClassName.get("$QUERY_PACKAGE.cache", "ModelCache"); + public static final ClassName MULTI_KEY_CACHE_CONVERTER = ClassName.get("$QUERY_PACKAGE.cache", "MultiKeyCacheConverter"); + public static final ClassName SIMPLE_MAP_CACHE = ClassName.get("$QUERY_PACKAGE.cache", "SimpleMapCache"); + + public static final ClassName CACHEABLE_MODEL_LOADER = ClassName.get(QUERIABLE, "CacheableModelLoader"); + public static final ClassName SINGLE_MODEL_LOADER = ClassName.get(QUERIABLE, "SingleModelLoader"); + public static final ClassName CACHEABLE_LIST_MODEL_LOADER = ClassName.get(QUERIABLE, "CacheableListModelLoader"); + public static final ClassName LIST_MODEL_LOADER = ClassName.get(QUERIABLE, "ListModelLoader"); + public static final ClassName CACHE_ADAPTER = ClassName.get(ADAPTER, "CacheAdapter"); + + public static final ClassName DATABASE_WRAPPER = ClassName.get(DATABASE, "DatabaseWrapper"); + + public static final ClassName SQLITE = ClassName.get(QUERY_PACKAGE, "SQLite"); + + public static final ClassName CACHEABLE_LIST_MODEL_SAVER = ClassName.get(SAVEABLE, "CacheableListModelSaver"); + public static final ClassName SINGLE_MODEL_SAVER = ClassName.get(SAVEABLE, "ModelSaver"); + + public static final ClassName SINGLE_KEY_CACHEABLE_MODEL_LOADER = ClassName.get(QUERIABLE, "SingleKeyCacheableModelLoader"); + public static final ClassName SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER = ClassName.get(QUERIABLE, "SingleKeyCacheableListModelLoader"); + + public static final ClassName NON_NULL = ClassName.get("android.support.annotation", "NonNull"); + public static final ClassName NON_NULL_X = ClassName.get("androidx.annotation", "NonNull"); + + public static final ClassName GENERATED = ClassName.get("javax.annotation", "Generated"); + + public static final ClassName STRING_UTILS = ClassName.get(BASE_PACKAGE, "StringUtils"); +} diff --git a/processor/src/main/java/com/dbflow5/processor/DBFlowProcessor.java b/processor/src/main/java/com/dbflow5/processor/DBFlowProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..73d423f45d4c27c272f948d747ca4c601c3ed1d9 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/DBFlowProcessor.java @@ -0,0 +1,99 @@ +package com.dbflow5.processor; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ColumnIgnore; +import com.dbflow5.annotation.Fts3; +import com.dbflow5.annotation.Fts4; +import com.dbflow5.annotation.Migration; +import com.dbflow5.annotation.ModelView; +import com.dbflow5.annotation.MultipleManyToMany; +import com.dbflow5.annotation.QueryModel; +import com.dbflow5.annotation.Table; +import com.dbflow5.annotation.TypeConverter; +import com.dbflow5.contentprovider.annotation.ContentProvider; +import com.dbflow5.contentprovider.annotation.TableEndpoint; +import com.dbflow5.processor.definition.DatabaseHolderDefinition; +import com.google.auto.service.AutoService; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import java.util.LinkedHashSet; +import java.util.Set; + +@AutoService(Processor.class) +public class DBFlowProcessor extends AbstractProcessor { + + private ProcessorManager manager; + + /** + * If the com.dbflow5.processor class is annotated with [ ], return an unmodifiable set with the + * same set of strings as the annotation. If the class is not so + * annotated, an empty set is returned. + * + * @return the names of the annotation types supported by this + * * com.dbflow5.processor, or an empty set if none + */ + @Override + public Set getSupportedAnnotationTypes() { + Set set = new LinkedHashSet<>(); + set.add(Table.class.getCanonicalName()); + set.add(Column.class.getCanonicalName()); + set.add(TypeConverter.class.getCanonicalName()); + set.add(ModelView.class.getCanonicalName()); + set.add(Migration.class.getCanonicalName()); + set.add(ContentProvider.class.getCanonicalName()); + set.add(TableEndpoint.class.getCanonicalName()); + set.add(ColumnIgnore.class.getCanonicalName()); + set.add(QueryModel.class.getCanonicalName()); + set.add(Fts3.class.getCanonicalName()); + set.add(Fts4.class.getCanonicalName()); + set.add(MultipleManyToMany.class.getCanonicalName()); + + return set; + } + + @Override + public Set getSupportedOptions() { + Set set = new LinkedHashSet<>(); + set.add(DatabaseHolderDefinition.OPTION_TARGET_MODULE_NAME); + return set; + } + + /** + * If the com.dbflow5.processor class is annotated with [ ], return the source version in the + * annotation. If the class is not so annotated, [ ][javax.lang.model.SourceVersion.RELEASE_6] is returned. + * + * @return the latest source version supported by this com.dbflow5.processor + */ + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + manager = new ProcessorManager(processingEnv); + manager.addHandlers(new Handlers.MigrationHandler(), + new Handlers.TypeConverterHandler(), + new Handlers.DatabaseHandler(), + new Handlers.TableHandler(), + new Handlers.QueryModelHandler(), + new Handlers.ModelViewHandler(), + new Handlers.ContentProviderHandler(), + new Handlers.TableEndpointHandler()); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + manager.handle(manager, roundEnv); + + // return true if we successfully processed the Annotation. + return true; + } + +} diff --git a/processor/src/main/java/com/dbflow5/processor/Handlers.java b/processor/src/main/java/com/dbflow5/processor/Handlers.java new file mode 100644 index 0000000000000000000000000000000000000000..a0bb08a45607c7208b77739feaafa46495d72f86 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/Handlers.java @@ -0,0 +1,309 @@ +package com.dbflow5.processor; + +import com.dbflow5.annotation.Database; +import com.dbflow5.annotation.ManyToMany; +import com.dbflow5.annotation.Migration; +import com.dbflow5.annotation.ModelView; +import com.dbflow5.annotation.MultipleManyToMany; +import com.dbflow5.annotation.QueryModel; +import com.dbflow5.annotation.Table; +import com.dbflow5.annotation.TypeConverter; +import com.dbflow5.contentprovider.annotation.ContentProvider; +import com.dbflow5.contentprovider.annotation.TableEndpoint; +import com.dbflow5.converter.TypeConverters; +import com.dbflow5.processor.definition.DatabaseDefinition; +import com.dbflow5.processor.definition.ManyToManyDefinition; +import com.dbflow5.processor.definition.MigrationDefinition; +import com.dbflow5.processor.definition.ModelViewDefinition; +import com.dbflow5.processor.definition.QueryModelDefinition; +import com.dbflow5.processor.definition.TableDefinition; +import com.dbflow5.processor.definition.TypeConverterDefinition; +import com.dbflow5.processor.definition.provider.ContentProviderDefinition; +import com.dbflow5.processor.definition.provider.TableEndpointDefinition; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.stream.Collectors; + +public class Handlers { + + /** + * Description: The main base-level handler for performing some action when the + * [DBFlowProcessor.process] is called. + */ + interface Handler { + + /** + * Called when the process of the [DBFlowProcessor] is called + * + * @param processorManager The manager that holds processing information + * * + * @param roundEnvironment The round environment + */ + void handle(ProcessorManager processorManager, RoundEnvironment roundEnvironment); + } + + /** + * Description: The base handler than provides common callbacks into processing annotated top-level elements + */ + abstract static class AnnotatedHandler implements Handler { + private final Class annotationClass; + + public AnnotatedHandler(Class annotationClass) { + this.annotationClass = annotationClass; + } + + @Override + public void handle(ProcessorManager processorManager, RoundEnvironment roundEnvironment) { + Set annotatedElements = (Set) roundEnvironment.getElementsAnnotatedWith(annotationClass); + processElements(processorManager, annotatedElements); + if (annotatedElements.size() > 0) { + annotatedElements.forEach(element -> { + AnnotationClass annotation = element.getAnnotation(annotationClass); + if (annotation != null) { + onProcessElement(annotation, element, processorManager); + } + }); + afterProcessElements(processorManager); + } + } + + public void processElements(ProcessorManager processorManager, Set annotatedElements) { + + } + + protected abstract void onProcessElement(AnnotationClass annotation, Element element, ProcessorManager processorManager); + + public void afterProcessElements(ProcessorManager processorManager) { + + } + } + + /** + * Description: Handles [Migration] by creating [MigrationDefinition] + * and adds them to the [ProcessorManager] + */ + public static class MigrationHandler extends AnnotatedHandler { + + public MigrationHandler() { + super(Migration.class); + } + + @Override + public void onProcessElement(Migration annotation, Element element, ProcessorManager processorManager) { + if (element instanceof TypeElement) { + Migration migration = element.getAnnotation(Migration.class); + if (migration != null) { + MigrationDefinition migrationDefinition = new MigrationDefinition(migration, processorManager, (TypeElement) element); + processorManager.addMigrationDefinition(migrationDefinition); + } + } + } + } + + /** + * Description: Handles [ModelView] annotations, writing + * ModelViewAdapters, and adding them to the [ProcessorManager] + */ + public static class ModelViewHandler extends AnnotatedHandler { + public ModelViewHandler() { + super(ModelView.class); + } + + public void onProcessElement(ModelView annotation, Element element, ProcessorManager processorManager) { + if (element instanceof TypeElement) { + ModelView modelView = element.getAnnotation(ModelView.class); + if (modelView != null) { + ModelViewDefinition modelViewDefinition = new ModelViewDefinition(modelView, processorManager, (TypeElement) element); + processorManager.addModelViewDefinition(modelViewDefinition); + } + } + } + } + + /** + * Description: Handles [QueryModel] annotations, writing QueryModelAdapter, and + * adding them to the [ProcessorManager]. + */ + static class QueryModelHandler extends AnnotatedHandler { + + public QueryModelHandler() { + super(QueryModel.class); + } + + @Override + public void onProcessElement(QueryModel annotation, Element element, ProcessorManager processorManager) { + if (element instanceof TypeElement) { + QueryModel queryModel = element.getAnnotation(QueryModel.class); + if (queryModel != null) { + QueryModelDefinition queryModelDefinition = new QueryModelDefinition(queryModel, (TypeElement) element, processorManager); + processorManager.addQueryModelDefinition(queryModelDefinition); + } + } + } + } + + static class TableEndpointHandler extends AnnotatedHandler { + + private Validators.TableEndpointValidator validator = new Validators.TableEndpointValidator(); + + public TableEndpointHandler() { + super(TableEndpoint.class); + } + + @Override + public void onProcessElement(TableEndpoint annotation, Element element, ProcessorManager processorManager) { + // top-level only + if (element.getEnclosingElement() instanceof PackageElement) { + TableEndpointDefinition tableEndpointDefinition = new TableEndpointDefinition(annotation, element, processorManager); + if (validator.validate(processorManager, tableEndpointDefinition)) { + processorManager.putTableEndpointForProvider(tableEndpointDefinition); + } + } + } + } + + /** + * Description: Handles [Table] annotations, writing ModelAdapters, + * and adding them to the [ProcessorManager] + */ + static class TableHandler extends AnnotatedHandler { + + public TableHandler() { + super(Table.class); + } + + @Override + public void onProcessElement(Table annotation, Element element, ProcessorManager processorManager) { + if (element instanceof TypeElement) { + TableDefinition tableDefinition = new TableDefinition(annotation, processorManager, (TypeElement) element); + processorManager.addTableDefinition(tableDefinition); + + ManyToMany manyToMany = element.getAnnotation(ManyToMany.class); + if (manyToMany != null) { + ManyToManyDefinition manyToManyDefinition = new ManyToManyDefinition((TypeElement) element, processorManager, manyToMany); + processorManager.addManyToManyDefinition(manyToManyDefinition); + } + + if (element.getAnnotation(MultipleManyToMany.class) != null) { + MultipleManyToMany multipleManyToMany = element.getAnnotation(MultipleManyToMany.class); + if (multipleManyToMany != null) { + for (ManyToMany many : multipleManyToMany.value()) { + processorManager.addManyToManyDefinition(new ManyToManyDefinition((TypeElement) element, processorManager, many)); + } + } + } + } + } + } + + /** + * Description: Handles [TypeConverter] annotations, + * adding default methods and adding them to the [ProcessorManager] + */ + static class TypeConverterHandler extends AnnotatedHandler { + + private Set typeConverterElements = new HashSet<>(); + private Set typeConverterDefinitions = new HashSet<>(); + + public TypeConverterHandler() { + super(TypeConverter.class); + } + + @Override + public void processElements(ProcessorManager processorManager, Set annotatedElements) { + for (Class clazz : DEFAULT_TYPE_CONVERTERS) { + typeConverterElements.add(processorManager.elements.getTypeElement(clazz.getName())); + } + annotatedElements.addAll(typeConverterElements); + } + + public void onProcessElement(TypeConverter annotation, Element element, ProcessorManager processorManager) { + if (element instanceof TypeElement) { + ClassName className = ProcessorUtils.fromTypeMirror(element.asType(), processorManager); + if (className != null) { + TypeConverterDefinition definition = new TypeConverterDefinition(annotation, className, element.asType(), processorManager, typeConverterElements.contains(element)); + if (VALIDATOR.validate(processorManager, definition)) { + // allow user overrides from default. + // Check here if user already placed definition of same type, since default converters + // are added last. + LinkedHashMap converts = new LinkedHashMap<>(); + for (Map.Entry entry : processorManager.typeConverters.entrySet()) { + if (entry.getValue().modelTypeName == definition.modelTypeName) { + converts.put(entry.getKey(), entry.getValue()); + } + } + if (converts.isEmpty()) { + typeConverterDefinitions.add(definition); + } + } + } + } + } + + public void afterProcessElements(ProcessorManager processorManager) { + // validate multiple global registered do not exist. + Map> grouping = typeConverterDefinitions.stream().filter(typeConverterDefinition -> typeConverterDefinition.isDefaultConverter) + .collect(Collectors.groupingBy(typeConverterDefinition -> typeConverterDefinition.modelTypeName)); + + // sort default converters first so that they can get overwritten + typeConverterDefinitions + .stream().sorted(Comparator.comparing(typeConverterDefinition -> !typeConverterDefinition.isDefaultConverter)) + .forEach(processorManager::addTypeConverterDefinition); + } + + private static final Validators.TypeConverterValidator VALIDATOR = new Validators.TypeConverterValidator(); + private static final Class[] DEFAULT_TYPE_CONVERTERS = new Class[]{TypeConverters.CalendarConverter.class, TypeConverters.BigDecimalConverter.class, TypeConverters.BigIntegerConverter.class, + TypeConverters.DateConverter.class, TypeConverters.SqlDateConverter.class, TypeConverters.BooleanConverter.class, TypeConverters.UUIDConverter.class, + TypeConverters.CharConverter.class + }; + } + + static class ContentProviderHandler extends Handlers.AnnotatedHandler { + + public ContentProviderHandler() { + super(ContentProvider.class); + } + + @Override + public void onProcessElement(ContentProvider annotation, Element element, ProcessorManager processorManager) { + ContentProviderDefinition contentProviderDefinition = new ContentProviderDefinition(annotation, element, processorManager); + if (contentProviderDefinition.elementClassName != null) { + processorManager.addContentProviderDefinition(contentProviderDefinition); + } + } + } + + /** + * Description: Deals with writing database definitions + */ + public static class DatabaseHandler extends Handlers.AnnotatedHandler { + + private final Validators.DatabaseValidator validator = new Validators.DatabaseValidator(); + + public DatabaseHandler() { + super(Database.class); + } + + public void onProcessElement(Database annotation, Element element, ProcessorManager processorManager) { + DatabaseDefinition managerWriter = new DatabaseDefinition(annotation, processorManager, element); + if (validator.validate(processorManager, managerWriter)) { + processorManager.addDatabaseDefinition(managerWriter); + } + } + + public static final String TYPE_CONVERTER_MAP_FIELD_NAME = "typeConverters"; + public static final String MODEL_ADAPTER_MAP_FIELD_NAME = "modelAdapters"; + public static final String QUERY_MODEL_ADAPTER_MAP_FIELD_NAME = "queryModelAdapterMap"; + public static final String MIGRATION_FIELD_NAME = "migrationMap"; + public static final String MODEL_VIEW_ADAPTER_MAP_FIELD_NAME = "modelViewAdapterMap"; + public static final String MODEL_NAME_MAP = "modelTableNames"; + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/MethodDefinition.java b/processor/src/main/java/com/dbflow5/processor/MethodDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..937ae157c2cedf6b72130e3a0870f37983f0a435 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/MethodDefinition.java @@ -0,0 +1,7 @@ +package com.dbflow5.processor; + +import com.squareup.javapoet.MethodSpec; + +public interface MethodDefinition { + MethodSpec methodSpec(); +} diff --git a/processor/src/main/java/com/dbflow5/processor/ProcessorManager.java b/processor/src/main/java/com/dbflow5/processor/ProcessorManager.java new file mode 100644 index 0000000000000000000000000000000000000000..ea6164d5889a14d6897203008608aa29a83e2193 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/ProcessorManager.java @@ -0,0 +1,416 @@ +package com.dbflow5.processor; + +import com.dbflow5.StringUtils; +import com.dbflow5.processor.definition.DatabaseDefinition; +import com.dbflow5.processor.definition.DatabaseHolderDefinition; +import com.dbflow5.processor.definition.DatabaseObjectHolder; +import com.dbflow5.processor.definition.EntityDefinition; +import com.dbflow5.processor.definition.ManyToManyDefinition; +import com.dbflow5.processor.definition.MigrationDefinition; +import com.dbflow5.processor.definition.ModelViewDefinition; +import com.dbflow5.processor.definition.QueryModelDefinition; +import com.dbflow5.processor.definition.TableDefinition; +import com.dbflow5.processor.definition.TypeConverterDefinition; +import com.dbflow5.processor.definition.provider.ContentProviderDefinition; +import com.dbflow5.processor.definition.provider.TableEndpointDefinition; +import com.dbflow5.processor.utils.WriterUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeName; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.processing.FilerException; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + +/** + * Description: The main object graph during processing. This class collects all of the + * com.dbflow5.processor classes and writes them to the corresponding database holders. + */ +public class ProcessorManager implements Handlers.Handler { + + public static ProcessorManager manager; + + public ProcessingEnvironment processingEnvironment; + private final List uniqueDatabases = new ArrayList<>(); + private final Map modelToDatabaseMap = new HashMap<>(); + public final LinkedHashMap typeConverters = new LinkedHashMap<>(); + private final Map>> migrations = new HashMap<>(); + + private final Map databaseDefinitionMap = new HashMap<>(); + private final Set> handlers = new HashSet<>(); + private final Map providerMap = new HashMap<>(); + + public ProcessorManager(ProcessingEnvironment processingEnvironment) { + this.processingEnvironment = processingEnvironment; + manager = this; + + messager = processingEnvironment.getMessager(); + typeUtils = processingEnvironment.getTypeUtils(); + elements = processingEnvironment.getElementUtils(); + } + + public void addHandlers(Handlers.AnnotatedHandler... containerHandlers) { + handlers.addAll(Arrays.asList(containerHandlers)); + } + + public Messager messager; + + public Types typeUtils; + + public Elements elements; + + public void addDatabase(TypeName database) { + if (!uniqueDatabases.contains(database)) { + uniqueDatabases.add(database); + } + } + + public void addDatabaseDefinition(DatabaseDefinition databaseDefinition) { + DatabaseObjectHolder holderDefinition = getOrPutDatabase(databaseDefinition.elementClassName); + if(holderDefinition != null) { + holderDefinition.databaseDefinition = databaseDefinition; + } + } + + public List getDatabaseHolderDefinitionList() { + return (List) databaseDefinitionMap.values(); + } + + public DatabaseObjectHolder getDatabaseHolderDefinition(TypeName databaseName) { + return databaseDefinitionMap.get(databaseName); + } + + public void addTypeConverterDefinition(TypeConverterDefinition definition) { + typeConverters.put(definition.modelTypeName, definition); + } + + public TypeConverterDefinition getTypeConverterDefinition(TypeName typeName) { + return typeConverters.get(typeName); + } + + public void addModelToDatabase(TypeName modelType, TypeName databaseName) { + if(modelType != null) { + addDatabase(databaseName); + modelToDatabaseMap.put(modelType, databaseName); + } + } + + public void addQueryModelDefinition(QueryModelDefinition queryModelDefinition) { + ClassName it = queryModelDefinition.elementClassName; + if(it != null) { + getOrPutDatabase(queryModelDefinition.associationalBehavior().databaseTypeName).queryModelDefinitionMap.put(it, queryModelDefinition); + } + } + + public void addTableDefinition(TableDefinition tableDefinition) { + if(tableDefinition.elementClassName != null) { + DatabaseObjectHolder holderDefinition = getOrPutDatabase(tableDefinition.associationalBehavior().databaseTypeName); + if(holderDefinition.tableDefinitionMap != null) { + holderDefinition.tableDefinitionMap.put(tableDefinition.elementClassName, tableDefinition); + } + if(holderDefinition.tableNameMap != null) { + String tableName = tableDefinition.associationalBehavior().name; + if (holderDefinition.tableNameMap.containsKey(tableName)) { + logError("Found duplicate table "+tableName+" " + + "for database " + holderDefinition.databaseDefinition.elementName); + } else { + holderDefinition.tableNameMap.put(tableName, tableDefinition); + } + } + } + } + + public void addManyToManyDefinition(ManyToManyDefinition manyToManyDefinition) { + DatabaseObjectHolder databaseHolderDefinition = getOrPutDatabase(manyToManyDefinition.databaseTypeName); + if(databaseHolderDefinition.manyToManyDefinitionMap != null && manyToManyDefinition.elementClassName != null) { + databaseHolderDefinition.manyToManyDefinitionMap.getOrDefault(manyToManyDefinition.elementClassName, new ArrayList<>()).add(manyToManyDefinition); + } + } + + public TableDefinition getTableDefinition(TypeName databaseName, TypeName typeName) { + return getOrPutDatabase(databaseName).tableDefinitionMap != null? getOrPutDatabase(databaseName).tableDefinitionMap.get(typeName): null; + } + + public QueryModelDefinition getQueryModelDefinition(TypeName databaseName, TypeName typeName) { + Map map = getOrPutDatabase(databaseName).queryModelDefinitionMap; + if(map != null) { + return map.get(typeName); + } + return null; + } + + public ModelViewDefinition getModelViewDefinition(TypeName databaseName, TypeName typeName) { + Map map = getOrPutDatabase(databaseName).modelViewDefinitionMap; + return map != null? map.get(typeName) : null; + } + + public EntityDefinition getReferenceDefinition(TypeName databaseName, TypeName typeName) { + TableDefinition tableDefinition = getTableDefinition(databaseName, typeName); + if(tableDefinition != null){ + return tableDefinition; + }else { + QueryModelDefinition queryModelDefinition = getQueryModelDefinition(databaseName, typeName); + if(queryModelDefinition != null) { + return queryModelDefinition; + }else { + return getModelViewDefinition(databaseName, typeName); + } + } + } + + public void addModelViewDefinition(ModelViewDefinition modelViewDefinition) { + if(modelViewDefinition.elementClassName != null) { + DatabaseObjectHolder holder = getOrPutDatabase(modelViewDefinition.associationalBehavior().databaseTypeName); + if(holder != null && holder.modelViewDefinitionMap != null) { + holder.modelViewDefinitionMap.put(modelViewDefinition.elementClassName, modelViewDefinition); + } + + } + } + + public List getTypeConverters() { + List list = new ArrayList<>(typeConverters.values()); + list.sort(Comparator.comparing(o -> o.modelTypeName.toString())); + return list; + } + + public List getTableDefinitions(TypeName databaseName) { + List list = new ArrayList<>(); + DatabaseObjectHolder databaseHolderDefinition = getOrPutDatabase(databaseName); + if(databaseHolderDefinition.tableDefinitionMap != null) { + list = new ArrayList<>(databaseHolderDefinition.tableDefinitionMap.values()); + list.sort(Comparator.comparing(tableDefinition -> tableDefinition.outputClassName.simpleName())); + } + + return list; + } + + public void setTableDefinitions(Map tableDefinitionSet, TypeName databaseName) { + DatabaseObjectHolder databaseDefinition = getOrPutDatabase(databaseName); + databaseDefinition.tableDefinitionMap = tableDefinitionSet; + } + + public List getModelViewDefinitions(TypeName databaseName) { + List list = new ArrayList<>(); + DatabaseObjectHolder databaseDefinition = getOrPutDatabase(databaseName); + if(databaseDefinition.modelViewDefinitionMap != null){ + list = new ArrayList<>(databaseDefinition.modelViewDefinitionMap.values()); + list.sort(Comparator.comparing(o -> o.outputClassName.simpleName())); + list.sort(Comparator.comparing(o -> o.priority)); + } + + return list; + } + + public void setModelViewDefinitions(Map modelViewDefinitionMap, ClassName elementClassName) { + DatabaseObjectHolder databaseDefinition = getOrPutDatabase(elementClassName); + databaseDefinition.modelViewDefinitionMap = modelViewDefinitionMap; + } + + public List getQueryModelDefinitions(TypeName databaseName) { + List list = new ArrayList<>(); + DatabaseObjectHolder databaseDefinition = getOrPutDatabase(databaseName); + if(databaseDefinition.queryModelDefinitionMap != null) { + list = new ArrayList<>(databaseDefinition.queryModelDefinitionMap.values()); + list.sort(Comparator.comparing(queryModelDefinition -> queryModelDefinition.outputClassName.simpleName())); + } + + return list; + } + + public void addMigrationDefinition(MigrationDefinition migrationDefinition) { + Map> migrationDefinitionMap = migrations.getOrDefault(migrationDefinition.databaseName, new HashMap<>()); + List migrationDefinitions = migrationDefinitionMap.getOrDefault(migrationDefinition.version, new ArrayList<>()); + if (!migrationDefinitions.contains(migrationDefinition)) { + migrationDefinitions.add(migrationDefinition); + } + } + + public Map> getMigrationsForDatabase(TypeName databaseName) { + Map> map = migrations.get(databaseName); + if(map != null) { + return map; + }else { + return new HashMap<>(); + } + } + + public void addContentProviderDefinition(ContentProviderDefinition contentProviderDefinition) { + if(contentProviderDefinition.elementTypeName != null) { + DatabaseObjectHolder holderDefinition = getOrPutDatabase(contentProviderDefinition.databaseTypeName); + if(holderDefinition.providerMap != null) { + holderDefinition.providerMap.put(contentProviderDefinition.elementTypeName, contentProviderDefinition); + } + providerMap.put(contentProviderDefinition.elementTypeName, contentProviderDefinition); + } + } + + public void putTableEndpointForProvider(TableEndpointDefinition tableEndpointDefinition) { + ContentProviderDefinition contentProviderDefinition = providerMap.get(tableEndpointDefinition.contentProviderName); + if (contentProviderDefinition == null) { + logError("Content Provider "+tableEndpointDefinition.contentProviderName+" was not found for the @TableEndpoint " + tableEndpointDefinition.elementClassName); + } else { + contentProviderDefinition.endpointDefinitions.add(tableEndpointDefinition); + } + } + + public void logError(Class callingClass, String error, Object... args) { + messager.printMessage(Diagnostic.Kind.ERROR, + String.format((callingClass!=null?callingClass.toString() : "") + // don't print this in logs. + .replace("(Java reflection is not available)", "") + + ":" + (error != null? error.trim() : ""), args)); + } + + public void logError(String error) { + logError( null, error); + } + + public void logWarning(String error) { + messager.printMessage(Diagnostic.Kind.WARNING, error != null? error : ""); + } + + public void logWarning(Class callingClass, String error) { + logWarning(callingClass + " : " + error); + } + + private DatabaseObjectHolder getOrPutDatabase(TypeName databaseName) { + return databaseDefinitionMap.getOrDefault(databaseName, new DatabaseObjectHolder()); + } + + @Override + public void handle(ProcessorManager processorManager, RoundEnvironment roundEnvironment) { + handlers.forEach(annotatedHandler -> { + annotatedHandler.handle(processorManager, roundEnvironment); + }); + + List databaseHolderDefinitionList = getDatabaseHolderDefinitionList(); + databaseHolderDefinitionList.sort(Comparator.comparing(databaseObjectHolder -> databaseObjectHolder.databaseDefinition.outputClassName.simpleName())); + for (DatabaseObjectHolder databaseHolderDefinition : databaseHolderDefinitionList) { + try { + + if (databaseHolderDefinition.databaseDefinition == null) { + manager.logError(StringUtils.joinToString(databaseHolderDefinition.getMissingDBRefs(),"\n")); + continue; + } + + Collection> manyToManyDefinitions = databaseHolderDefinition.manyToManyDefinitionMap.values(); + + List sumList = new ArrayList<>(); + for (List list : manyToManyDefinitions) { + sumList.addAll(list); + } + List sortedList = sumList.stream().sorted(Comparator.comparing(manyToManyDefinition -> manyToManyDefinition.outputClassName.simpleName())).collect(Collectors.toList()); + for (ManyToManyDefinition manyToManyList : sortedList) { + manyToManyList.prepareForWrite(); + WriterUtils.writeBaseDefinition(manyToManyList, processorManager); + } + + // process all in next round. + if (!manyToManyDefinitions.isEmpty()) { + manyToManyDefinitions.clear(); + continue; + } + + if (roundEnvironment.processingOver()) { + Validators.ContentProviderValidator validator = new Validators.ContentProviderValidator(); + List contentProviderDefinitions = databaseHolderDefinition.providerMap.values() + .stream().sorted(Comparator.comparing(it -> it.outputClassName.simpleName())).collect(Collectors.toList()); + contentProviderDefinitions.forEach(contentProviderDefinition -> { + if (validator.validate(processorManager, contentProviderDefinition)) { + WriterUtils.writeBaseDefinition(contentProviderDefinition, processorManager); + } + }); + } + + if(databaseHolderDefinition.databaseDefinition != null) { + databaseHolderDefinition.databaseDefinition.validateAndPrepareToWrite(); + } + + if (roundEnvironment.processingOver()) { + if(databaseHolderDefinition.databaseDefinition != null) { + if (databaseHolderDefinition.databaseDefinition.outputClassName != null) { + JavaFile.builder(databaseHolderDefinition.databaseDefinition.packageName, databaseHolderDefinition.databaseDefinition.typeSpec()).build() + .writeTo(processorManager.processingEnvironment.getFiler()); + } + } + } + + List tableDefinitions = databaseHolderDefinition.tableDefinitionMap.values() + .stream().sorted(Comparator.comparing(tableDefinition -> tableDefinition.outputClassName.simpleName())).collect(Collectors.toList()); + + tableDefinitions.forEach(tableDefinition -> WriterUtils.writeBaseDefinition(tableDefinition, processorManager)); + + Collection modelViewDefinitions = databaseHolderDefinition.modelViewDefinitionMap.values(); + modelViewDefinitions + .stream().sorted((a, b) -> a.priority > b.priority ? -1 : 1) + .forEach(modelViewDefinition -> WriterUtils.writeBaseDefinition(modelViewDefinition, processorManager)); + + List queryModelDefinitions = databaseHolderDefinition.queryModelDefinitionMap.values() + .stream().sorted(Comparator.comparing(queryModelDefinition -> queryModelDefinition.outputClassName.simpleName())).collect(Collectors.toList()); + queryModelDefinitions.forEach(queryModelDefinition -> WriterUtils.writeBaseDefinition(queryModelDefinition, processorManager) ); + + EntityDefinition.safeWritePackageHelper(tableDefinitions, processorManager); + EntityDefinition.safeWritePackageHelper(modelViewDefinitions, processorManager); + EntityDefinition.safeWritePackageHelper(queryModelDefinitions, processorManager); + } catch (IOException e) { + } + } + + try { + DatabaseHolderDefinition databaseHolderDefinition = new DatabaseHolderDefinition(processorManager); + if (!databaseHolderDefinition.isGarbage()) { + JavaFile.builder(com.dbflow5.processor.ClassNames.FLOW_MANAGER_PACKAGE, + databaseHolderDefinition.typeSpec()).build() + .writeTo(processorManager.processingEnvironment.getFiler()); + } + } catch (FilerException ignored) { + } catch (IOException e) { + logError(e.getMessage()); + } + } + + public boolean elementBelongsInTable(Element element) { + Element enclosingElement = element.getEnclosingElement(); + EntityDefinition find = null; + + Collection holders = databaseDefinitionMap.values(); + for (DatabaseObjectHolder holder : holders) { + // table check. + Collection tableDefinitions = holder.tableDefinitionMap.values(); + for(TableDefinition tableDefinition : tableDefinitions) { + if(tableDefinition.element == enclosingElement) { + find = tableDefinition; + break; + } + } + + // modelview check. + Collection modelViewDefinitions = holder.modelViewDefinitionMap.values(); + for(ModelViewDefinition modelViewDefinition : modelViewDefinitions) { + if(modelViewDefinition.element == enclosingElement) { + find = modelViewDefinition; + break; + } + } + + // querymodel check. + Collection queryModelDefinitions = holder.queryModelDefinitionMap.values(); + for(QueryModelDefinition queryModelDefinition : queryModelDefinitions) { + if(queryModelDefinition.element == enclosingElement) { + find = queryModelDefinition; + break; + } + } + } + return find != null; + } + +} diff --git a/processor/src/main/java/com/dbflow5/processor/SQLiteHelper.java b/processor/src/main/java/com/dbflow5/processor/SQLiteHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..8142499a29963612780df69ad92fa455523d6f5f --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/SQLiteHelper.java @@ -0,0 +1,124 @@ +package com.dbflow5.processor; + +import com.dbflow5.data.Blob; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Author: andrewgrosner + * Description: Holds the mapping between SQL data types and java classes used in the com.dbflow5.processor. + */ +public enum SQLiteHelper { + + INTEGER { + final String sqLiteStatementMethod = "Long"; + + final String sqliteStatementWrapperMethod = "Number"; + }, + REAL { + final String sqLiteStatementMethod = "Double"; + }, + TEXT { + final String sqLiteStatementMethod = "String"; + }, + BLOB { + final String sqLiteStatementMethod = "Blob"; + }; + + public String sqLiteStatementMethod; + + public String sqliteStatementWrapperMethod = sqLiteStatementMethod; + + private static final Map sTypeMap = new HashMap(){{ + put(TypeName.BYTE, INTEGER); + put(TypeName.SHORT, INTEGER); + put(TypeName.INT, INTEGER); + put(TypeName.LONG, INTEGER); + put(TypeName.FLOAT, REAL); + put(TypeName.DOUBLE, REAL); + put(TypeName.BOOLEAN, INTEGER); + put(TypeName.CHAR, TEXT); + put(ArrayTypeName.of(TypeName.BYTE), BLOB); + put(TypeName.BYTE.box(), INTEGER); + put(TypeName.SHORT.box(), INTEGER); + put(TypeName.INT.box(), INTEGER); + put(TypeName.LONG.box(), INTEGER); + put(TypeName.FLOAT.box(), REAL); + put(TypeName.DOUBLE.box(), REAL); + put(TypeName.BOOLEAN.box(), INTEGER); + put(TypeName.CHAR.box(), TEXT); + put(ClassName.get(String.class), TEXT); + put(ArrayTypeName.of(TypeName.BYTE.box()), BLOB); + put(ArrayTypeName.of(TypeName.BYTE), BLOB); + put(ClassName.get(Blob.class), BLOB); + }}; + + + private static final Map sMethodMap = new HashMap(){{ + put(ArrayTypeName.of(TypeName.BYTE), "getBlob"); + put(ArrayTypeName.of(TypeName.BYTE.box()), "getBlob"); + put(TypeName.BOOLEAN, "getBoolean"); + put(TypeName.BYTE, "getInt"); + put(TypeName.BYTE.box(), "getInt"); + put(TypeName.CHAR, "getString"); + put(TypeName.CHAR.box(), "getString"); + put(TypeName.DOUBLE, "getDouble"); + put(TypeName.DOUBLE.box(), "getDouble"); + put(TypeName.FLOAT, "getFloat"); + put(TypeName.FLOAT.box(), "getFloat"); + put(TypeName.INT, "getInt"); + put(TypeName.INT.box(), "getInt"); + put(TypeName.LONG, "getLong"); + put(TypeName.LONG.box(), "getLong"); + put(TypeName.SHORT, "getShort"); + put(TypeName.SHORT.box(), "getShort"); + put(ClassName.get(String.class), "getString"); + put(ClassName.get(Blob.class), "getBlob"); + }}; + + private static final HashSet sNumberMethodList = new HashSet(){{ + add(TypeName.BYTE); + add(TypeName.DOUBLE); + add(TypeName.FLOAT); + add(TypeName.LONG); + add(TypeName.SHORT); + add(TypeName.INT); + }}; + + public static SQLiteHelper get(TypeName typeName) { + if (sTypeMap.get(typeName) != null) { + return sTypeMap.get(typeName); + } + throw new IllegalArgumentException("Cannot map "+typeName+" to a SQLite Type. If this is a " + + "TypeConverter, ensure it maps to a primitive type."); + } + + public static String getWrapperMethod(TypeName typeName) { + String sqLiteHelper = get(typeName).sqliteStatementWrapperMethod; + if (typeName == TypeName.FLOAT.box()) { + sqLiteHelper = "Float"; + } + return sqLiteHelper; + } + + public static boolean containsType(TypeName typeName) { + return sTypeMap.containsKey(typeName); + } + + public static boolean containsMethod(TypeName typeName) { + return sMethodMap.containsKey(typeName); + } + + public static String getMethod(TypeName typeName) { + return sMethodMap.get(typeName) == null? "" : sMethodMap.get(typeName); + } + + public static boolean containsNumberMethod(TypeName typeName) { + return sNumberMethodList.contains(typeName); + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/Validators.java b/processor/src/main/java/com/dbflow5/processor/Validators.java new file mode 100644 index 0000000000000000000000000000000000000000..bcc6b661799edba22bfd2adf4bfd447e4892d4c5 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/Validators.java @@ -0,0 +1,264 @@ +package com.dbflow5.processor; + +import com.dbflow5.processor.definition.DatabaseDefinition; +import com.dbflow5.processor.definition.ModelViewDefinition; +import com.dbflow5.processor.definition.OneToManyDefinition; +import com.dbflow5.processor.definition.TableDefinition; +import com.dbflow5.processor.definition.TypeConverterDefinition; +import com.dbflow5.processor.definition.column.ColumnAccessor; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.definition.column.ReferenceColumnDefinition; +import com.dbflow5.processor.definition.provider.ContentProviderDefinition; +import com.dbflow5.processor.definition.provider.TableEndpointDefinition; +import com.squareup.javapoet.TypeName; + +public class Validators { + + /** + * Description: the base interface for validating annotations. + */ + interface Validator { + + /** + * @param processorManager The manager + * * + * @param validatorDefinition The validator to use + * * + * @return true if validation passed, false if there was an error. + */ + boolean validate(ProcessorManager processorManager, ValidatorDefinition validatorDefinition); + } + + + /** + * Description: Ensures the integrity of the annotation com.dbflow5.processor for columns. + * + * @author Andrew Grosner (fuzz) + */ + public static class ColumnValidator implements Validator { + + private ColumnDefinition autoIncrementingPrimaryKey = null; + + @Override + public boolean validate(ProcessorManager processorManager, ColumnDefinition validatorDefinition) { + + boolean success = true; + + // validate getter and setters. + if (validatorDefinition.columnAccessor instanceof ColumnAccessor.PrivateScopeColumnAccessor) { + ColumnAccessor.PrivateScopeColumnAccessor privateColumnAccess = (ColumnAccessor.PrivateScopeColumnAccessor) validatorDefinition.columnAccessor; + if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getterNameElement())) { + success = false; + } + if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.setterNameElement())) { + success = false; + } + } + + if (!validatorDefinition.defaultValue.isEmpty()) { + TypeName typeName = validatorDefinition.elementTypeName; + if (validatorDefinition instanceof ReferenceColumnDefinition && ((ReferenceColumnDefinition) validatorDefinition).isReferencingTableObject) { + processorManager.logError(ColumnValidator.class, "Default values cannot be specified for model fields"); + } else if (typeName != null && typeName.isPrimitive()) { + processorManager.logWarning(ColumnValidator.class, + "Default value of ${validatorDefinition.defaultValue} from" + + " ${validatorDefinition.entityDefinition.elementName}.${validatorDefinition.elementName}" + + " is ignored for primitive columns."); + } + } + + if (validatorDefinition.columnName.isEmpty()) { + success = false; + processorManager.logError("Field ${validatorDefinition.elementName} " + + "cannot have a null column name for column: ${validatorDefinition.columnName}" + + " and type: ${validatorDefinition.elementTypeName}"); + } + + if (validatorDefinition.columnAccessor instanceof ColumnAccessor.EnumColumnAccessor) { + if (validatorDefinition.type instanceof ColumnDefinition.Type.Primary) { + success = false; + processorManager.logError("Enums cannot be primary keys. Column: ${validatorDefinition.columnName}" + + " and type: ${validatorDefinition.elementTypeName}"); + } else if (validatorDefinition instanceof ReferenceColumnDefinition) { + success = false; + processorManager.logError("Enums cannot be foreign keys. Column: ${validatorDefinition.columnName}" + + " and type: ${validatorDefinition.elementTypeName}"); + } + } + + if (validatorDefinition instanceof ReferenceColumnDefinition) { + if (validatorDefinition.column != null) { + if (!validatorDefinition.column.name().isEmpty()) { + success = false; + processorManager.logError("Foreign Key ${validatorDefinition.elementName} cannot specify the @Column.name() field. " + + "Use a @ForeignKeyReference(columnName = {NAME} instead. " + + "Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}"); + } + } + + // it is an error to specify both a not null and provide explicit references. + if (((ReferenceColumnDefinition) validatorDefinition).explicitReferences && validatorDefinition.notNull) { + success = false; + processorManager.logError("Foreign Key ${validatorDefinition.elementName} " + + "cannot specify both @NotNull and references. Remove the top-level @NotNull " + + "and use the contained 'notNull' field " + + "in each reference to control its SQL notnull conflicts."); + } + + } else { + if (autoIncrementingPrimaryKey != null && validatorDefinition.type instanceof ColumnDefinition.Type.Primary) { + processorManager.logError("You cannot mix and match autoincrementing and composite primary keys."); + success = false; + } + + if (validatorDefinition.type instanceof ColumnDefinition.Type.PrimaryAutoIncrement + || validatorDefinition.type instanceof ColumnDefinition.Type.RowId) { + if (autoIncrementingPrimaryKey == null) { + autoIncrementingPrimaryKey = validatorDefinition; + } else if (autoIncrementingPrimaryKey != validatorDefinition) { + processorManager.logError("Only one auto-incrementing primary key is allowed on a table. " + + "Found Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}"); + success = false; + } + } + } + + return success; + } + } + + /** + * Description: + */ + public static class ContentProviderValidator implements Validator { + @Override + public boolean validate(ProcessorManager processorManager, ContentProviderDefinition validatorDefinition) { + boolean success = true; + + if (validatorDefinition.endpointDefinitions.isEmpty()) { + processorManager.logError("The content provider ${validatorDefinition.element.simpleName} " + + "must have at least 1 @TableEndpoint associated with it"); + success = false; + } + + return success; + } + } + + /** + * Description: + */ + public static class DatabaseValidator implements Validator { + @Override + public boolean validate(ProcessorManager processorManager, DatabaseDefinition validatorDefinition) { + return true; + } + } + + /** + * Description: + */ + public static class ModelViewValidator implements Validator { + @Override + public boolean validate(ProcessorManager processorManager, ModelViewDefinition validatorDefinition) { + return true; + } + } + + /** + * Description: Validates to ensure a [OneToManyDefinition] is correctly coded. Will throw failures on the [ProcessorManager] + */ + public static class OneToManyValidator implements Validator { + public boolean validate(ProcessorManager processorManager, OneToManyDefinition validatorDefinition) { + return true; + } + } + + public static class TableEndpointValidator implements Validator { + + @Override + public boolean validate(ProcessorManager processorManager, TableEndpointDefinition validatorDefinition) { + boolean success = true; + + if (validatorDefinition.contentUriDefinitions.isEmpty()) { + processorManager.logError("A table endpoint ${validatorDefinition.elementClassName} " + + "must supply at least one @ContentUri"); + success = false; + } + + return success; + } + } + + /** + * Description: Validates proper usage of the [com.dbflow5.annotation.Table] + */ + public static class TableValidator implements Validator { + + @Override + public boolean validate(ProcessorManager processorManager, TableDefinition validatorDefinition) { + boolean success = true; + + if (!validatorDefinition.hasPrimaryConstructor) { + processorManager.logError(TableValidator.class, "Table ${validatorDefinition.elementClassName}" + + " must provide a visible, parameterless constructor. Each field also must have a visible " + + "setter for now."); + success = false; + } + + if (validatorDefinition.columnDefinitions.isEmpty()) { + processorManager.logError(TableValidator.class, + "Table ${validatorDefinition.associationalBehavior.name} " + + "of ${validatorDefinition.elementClassName}, ${validatorDefinition.element.javaClass} " + + "needs to define at least one column"); + success = false; + } + + boolean hasTwoKinds = (validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement + || validatorDefinition.primaryKeyColumnBehavior().hasRowID) + && !validatorDefinition._primaryColumnDefinitions.isEmpty(); + + if (hasTwoKinds) { + processorManager.logError(TableValidator.class, "Table ${validatorDefinition.associationalBehavior.name}" + + " cannot mix and match autoincrement and composite primary keys"); + success = false; + } + + boolean hasPrimary = (validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement + || validatorDefinition.primaryKeyColumnBehavior().hasRowID) + && validatorDefinition._primaryColumnDefinitions.isEmpty() + || !validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement + && !validatorDefinition.primaryKeyColumnBehavior().hasRowID + && !validatorDefinition._primaryColumnDefinitions.isEmpty(); + if (!hasPrimary && validatorDefinition.type == TableDefinition.Type.Normal) { + processorManager.logError(TableValidator.class, + "Table ${validatorDefinition.associationalBehavior.name} " + + "needs to define at least one primary key"); + success = false; + } + + return success; + } + } + + public static class TypeConverterValidator implements Validator { + @Override + public boolean validate(ProcessorManager processorManager, TypeConverterDefinition validatorDefinition) { + boolean success = true; + + if (validatorDefinition.modelTypeName == null) { + processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + + "unsupported Model Element parameter. If it has type parameters, you must " + + "remove them or subclass it for proper usage."); + success = false; + } else if (validatorDefinition.dbTypeName == null) { + processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + + "unsupported DB Element parameter. If it has type parameters, you must remove" + + " them or subclass it for proper usage."); + success = false; + } + + return success; + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/Adders.java b/processor/src/main/java/com/dbflow5/processor/definition/Adders.java new file mode 100644 index 0000000000000000000000000000000000000000..009bb1bbd5f2ccadd52afc14dda72e69ecfb132f --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/Adders.java @@ -0,0 +1,19 @@ +package com.dbflow5.processor.definition; + +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeSpec; + +public class Adders{ + /** + * Description: + * + * @author Andrew Grosner (fuzz) + */ + public interface TypeAdder { + void addToType(TypeSpec.Builder typeBuilder); + } + + public interface CodeAdder { + CodeBlock.Builder addCode(CodeBlock.Builder code); + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/BaseDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/BaseDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..de75b1d85ff30ec7a60821fb0b1fe628ce1a7288 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/BaseDefinition.java @@ -0,0 +1,215 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.DBFlowProcessor; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.utils.DependencyUtils; +import com.dbflow5.processor.utils.ElementExtensions; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Description: Holds onto a common-set of fields and provides a common-set of methods to output class files. + */ +public abstract class BaseDefinition implements TypeDefinition { + /** + * Original definition element. + */ + public Element element; + + /** + * Optional [TypeElement]. if the [element] passed in is a type. + */ + public TypeElement typeElement; + /** + * The resolved [TypeName] for this definition. It may be a return type if [ExecutableElement], + * + */ + public TypeName elementTypeName; + public ProcessorManager manager; + public String packageName; + + /** + * The [ClassName] referring to the type of the definition. This excludes primitives. + */ + public ClassName elementClassName = null; + + + public ClassName outputClassName = null; + + /** + * Unqualified name of the [element]. Useful for names of methods, fields, or short type names. + */ + public String elementName = null; + + public BaseDefinition(Element element, TypeElement typeElement, TypeName elementTypeName, ProcessorManager manager, String packageName) { + this.element = element; + this.typeElement = typeElement; + this.elementTypeName = elementTypeName; + this.manager = manager; + this.packageName = packageName; + + elementName = element.getSimpleName().toString(); + } + + public BaseDefinition(ExecutableElement element, ProcessorManager processorManager){ + manager = processorManager; + try { + packageName = processorManager.elements.getPackageOf(element).getQualifiedName().toString(); + if(packageName == null){ + packageName = ""; + } + }catch (Exception e){ + packageName = ""; + } + this.element = element; + typeElement = null; + try { + elementTypeName = TypeName.get(element.asType()); + } catch (IllegalArgumentException i) { + // unexpected TypeMirror (usually a List). Cannot use for TypeName. + elementTypeName = null; + } + + if(elementTypeName != null && elementTypeName.isPrimitive()){ + elementClassName = null; + }else { + elementClassName = ElementExtensions.toClassName(element, manager); + } + + elementName = element.getSimpleName().toString(); + } + + public BaseDefinition(Element element, ProcessorManager processorManager, String packageName) { + if(packageName == null){ + packageName = processorManager.elements.getPackageOf(element).getQualifiedName().toString(); + } + if(packageName == null){ + packageName = ""; + } + manager = processorManager; + this.element = element; + this.packageName = packageName; + if(element instanceof TypeElement){ + typeElement = ((TypeElement)element); + }else { + typeElement = ElementExtensions.toTypeElement(element, ProcessorManager.manager); + } + + try { + if(element instanceof ExecutableElement){ + elementTypeName = TypeName.get(((ExecutableElement) element).getReturnType()); + }else { + elementTypeName = TypeName.get(element.asType()); + } + } catch (IllegalArgumentException i) { + processorManager.logError("Found illegal type: ${element.asType()} for ${element.simpleName}"); + processorManager.logError("Exception here: $i"); + elementTypeName = null; + } + + if(elementTypeName != null && !elementTypeName.isPrimitive()){ + elementClassName = ElementExtensions.toClassName(element, processorManager); + } + + elementName = element.getSimpleName().toString(); + } + + public BaseDefinition(TypeElement element, ProcessorManager processorManager) { + manager = processorManager; + this.element = element; + this.typeElement = element; + try { + this.packageName = processorManager.elements.getPackageOf(element).getQualifiedName().toString(); + }catch (Exception e){ + this.packageName = ""; + } + this.elementTypeName = TypeName.get(element.asType()); + + + elementClassName = ElementExtensions.toClassName(element, processorManager); + + elementName = element.getSimpleName().toString(); + } + + public void setOutputClassName(String postfix) { + String outputName; + + ClassName elementClassName = this.elementClassName; + if (elementClassName == null) { + if(elementTypeName instanceof ClassName){ + outputName = ((ClassName) elementTypeName).simpleName(); + }else if(elementTypeName instanceof ParameterizedTypeName){ + outputName = ((ParameterizedTypeName) elementTypeName).rawType.simpleName(); + this.elementClassName = ((ParameterizedTypeName) elementTypeName).rawType; + }else { + outputName = elementTypeName.toString(); + } + } else { + outputName = elementClassName.simpleName(); + } + outputClassName = ClassName.get(packageName, outputName + postfix); + } + + protected void setOutputClassNameFull(String fullName) { + outputClassName = ClassName.get(packageName, fullName); + } + + public TypeSpec typeSpec() { + if (outputClassName == null) { + manager.logError("$elementTypeName's is missing an outputClassName. Database was " + + "${(this as? EntityDefinition)?.associationalBehavior?.databaseTypeName}"); + } + + return publicFinalClass((outputClassName != null && outputClassName.simpleName() != null) ? outputClassName.simpleName() : "", builder -> { + if (DependencyUtils.hasJavaX()) { + builder.addAnnotation(at(ClassNames.GENERATED, stringStringMap -> { + stringStringMap.put("value", "\"" + DBFlowProcessor.class.getCanonicalName() + "\""); + return null; + }).build()); + } + if(extendsClass() != null){ + builder.superclass(extendsClass()); + } + TypeName[] typeNames = implementsClasses(); + for(TypeName typeName : typeNames){ + builder.addSuperinterface(typeName); + } + builder.addJavadoc("This is generated code. Please do not modify"); + onWriteDefinition(builder); + return builder; + }); + } + + protected TypeName extendsClass() { + return null; + } + + protected TypeName[] implementsClasses() { + return new TypeName[]{}; + } + + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + + } + + private TypeSpec publicFinalClass(String className, Function typeSpecFunc) { + return typeSpecFunc.apply(TypeSpec.classBuilder(className)).addModifiers(Modifier.PUBLIC, Modifier.FINAL).build(); + } + + private AnnotationSpec.Builder at(ClassName className, Function, Void> mapFunc) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(className); + + Map map = new HashMap<>(); + mapFunc.apply(map); + map.forEach(builder::addMember); + return builder; + } +} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/BasicColumnGenerator.kt b/processor/src/main/java/com/dbflow5/processor/definition/BasicColumnGenerator.java similarity index 37% rename from processor/src/main/kotlin/com/dbflow5/processor/definition/BasicColumnGenerator.kt rename to processor/src/main/java/com/dbflow5/processor/definition/BasicColumnGenerator.java index ffb7b702a4b97bf7968d40f98707eaf3b03eff00..7740f3596cdb7743f3b17de10065720112053d70 100644 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/BasicColumnGenerator.kt +++ b/processor/src/main/java/com/dbflow5/processor/definition/BasicColumnGenerator.java @@ -1,34 +1,42 @@ -package com.dbflow5.processor.definition +package com.dbflow5.processor.definition; -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.ReferenceColumnDefinition -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.annotation -import javax.lang.model.element.Element +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ColumnMap; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.definition.column.ReferenceColumnDefinition; +import com.dbflow5.processor.utils.ElementUtility; +import javax.lang.model.element.Element; /** * Description: Generates a [ColumnDefinition] or [ReferenceColumnDefinition] based on the [Element] * passed into the [generate] method. */ -class BasicColumnGenerator(private val manager: ProcessorManager) { +public class BasicColumnGenerator { + private ProcessorManager manager; + + public BasicColumnGenerator(ProcessorManager manager) { + this.manager = manager; + } /** * Generates a [ColumnDefinition]. If null, there is a field that is package private not in the same package that we * did not generate code for to access. */ - fun generate(element: Element, entityDefinition: EntityDefinition): ColumnDefinition? { - val isColumnMap = element.annotation() != null - val isPackagePrivate = ElementUtility.isPackagePrivate(element) - val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, element, entityDefinition.element) + public ColumnDefinition generate(Element element, EntityDefinition entityDefinition) { + boolean isColumnMap = element.getAnnotation(ColumnMap.class) != null; + boolean isPackagePrivate = ElementUtility.isPackagePrivate(element); + boolean isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, element, entityDefinition.element); - if (checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage, element)) return null + if (checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage, element)) return null; - return if (isColumnMap) { - ReferenceColumnDefinition(element.annotation()!!, manager, entityDefinition, element, isPackagePrivateNotInSamePackage) + if (isColumnMap) { + return new ReferenceColumnDefinition(element.getAnnotation(ColumnMap.class), manager, entityDefinition, element, isPackagePrivateNotInSamePackage); } else { - ColumnDefinition(manager, element, entityDefinition, isPackagePrivateNotInSamePackage) + return new ColumnDefinition(manager, element, entityDefinition, isPackagePrivateNotInSamePackage, element.getAnnotation(Column.class), + element.getAnnotation(PrimaryKey.class), ConflictAction.NONE); } } @@ -36,13 +44,13 @@ class BasicColumnGenerator(private val manager: ProcessorManager) { * Do not support inheritance on package private fields without having ability to generate code for it in * same package. */ - private fun checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage: Boolean, element: Element): Boolean { + private boolean checkInheritancePackagePrivate(boolean isPackagePrivateNotInSamePackage, Element element) { if (isPackagePrivateNotInSamePackage && !manager.elementBelongsInTable(element)) { manager.logError("Package private inheritance on non-table/querymodel/view " + "is not supported without a @InheritedColumn annotation." + - " Make $element from ${element.enclosingElement} public or private.") - return true + " Make $element from ${element.enclosingElement} public or private."); + return true; } - return false + return false; } } \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/DatabaseDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..cbf22f42e37ce143ab5ccdb6bd245c36c2e184e9 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseDefinition.java @@ -0,0 +1,181 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.annotation.Database; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.Validators; +import com.dbflow5.processor.utils.JavaPoetExtensions; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Description: Writes [Database] definitions, + * which contain [Table], [ModelView], and [Migration] + */ +public class DatabaseDefinition extends BaseDefinition implements TypeDefinition { + + private final int databaseVersion; + private final boolean foreignKeysSupported; + private final boolean consistencyChecksEnabled; + private final boolean backupEnabled; + + public ConflictAction insertConflict; + public ConflictAction updateConflict; + + public DatabaseObjectHolder objectHolder = null; + + public DatabaseDefinition(Database database, ProcessorManager manager, Element element) { + super(element, manager, ClassNames.FLOW_MANAGER_PACKAGE); + + databaseVersion = database.version(); + foreignKeysSupported = database.foreignKeyConstraintsEnforced(); + consistencyChecksEnabled = database.consistencyCheckEnabled(); + backupEnabled = database.backupEnabled(); + + insertConflict = database.insertConflict(); + updateConflict = database.updateConflict(); + + setOutputClassName(elementName + "_Database"); + + if (!element.getModifiers().contains(Modifier.ABSTRACT) + || element.getModifiers().contains(Modifier.PRIVATE) + || !ProcessorUtils.isSubclass(typeElement, manager.processingEnvironment, ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME)) { + manager.logError(elementClassName + " must be a visible abstract class that " + + "extends " + ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME); + } + } + + @Override + public TypeName extendsClass() { + return elementClassName; + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + writeConstructor(typeBuilder); + writeGetters(typeBuilder); + } + + public void validateAndPrepareToWrite() { + prepareDefinitions(); + validateDefinitions(); + } + + private void validateDefinitions() { + if(elementClassName != null) { + Map map = new HashMap<>(); + Validators.TableValidator tableValidator = new Validators.TableValidator(); + manager.getTableDefinitions(elementClassName).stream() + .filter(tableDefinition -> tableValidator.validate(ProcessorManager.manager, tableDefinition)) + .forEach(tableDefinition -> { + if(tableDefinition.elementClassName != null) { + map.put(tableDefinition.elementClassName, tableDefinition); + } + }); + manager.setTableDefinitions(map, elementClassName); + + Map modelViewDefinitionMap = new HashMap<>(); + Validators.ModelViewValidator modelViewValidator = new Validators.ModelViewValidator(); + manager.getModelViewDefinitions(elementClassName) + .stream().filter(modelViewDefinition -> modelViewValidator.validate(ProcessorManager.manager, modelViewDefinition)).forEach(modelViewDefinition -> { + if(modelViewDefinition.elementClassName != null) { + modelViewDefinitionMap.put(modelViewDefinition.elementClassName, modelViewDefinition); + } + }); + + manager.setModelViewDefinitions(modelViewDefinitionMap, elementClassName); + } + } + + private void prepareDefinitions() { + if(elementClassName != null) { + manager.getTableDefinitions(elementClassName).forEach(TableDefinition::prepareForWrite); + manager.getModelViewDefinitions(elementClassName).forEach(ModelViewDefinition::prepareForWrite); + manager.getQueryModelDefinitions(elementClassName).forEach(QueryModelDefinition::prepareForWrite); + } + } + + private void writeConstructor(TypeSpec.Builder builder) { + MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder(); + ParameterSpec.Builder paramsBuilder = ParameterSpec.builder(ClassNames.DATABASE_HOLDER, "holder"); + methodBuilder.addParameter(paramsBuilder.build()); + + methodBuilder.addModifiers(Modifier.PUBLIC); + + if(elementClassName != null) { + for (TableDefinition definition : manager.getTableDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters()) { + methodBuilder.addStatement("addModelAdapter(new $T(holder, this), holder)", definition.outputClassName); + } else { + methodBuilder.addStatement("addModelAdapter(new $T(this), holder)", definition.outputClassName); + } + } + + for (ModelViewDefinition definition : manager.getModelViewDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters()) { + methodBuilder.addStatement("addModelViewAdapter(new $T(holder, this), holder)", definition.outputClassName); + } else { + methodBuilder.addStatement("addModelViewAdapter(new $T(this), holder)", definition.outputClassName); + } + } + + for (QueryModelDefinition definition : manager.getQueryModelDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters()) { + methodBuilder.addStatement("addRetrievalAdapter(new $T(holder, this), holder)", definition.outputClassName); + } else { + methodBuilder.addStatement("addRetrievalAdapter(new $T(this), holder)", definition.outputClassName); + } + } + + Map> migrationDefinitionMap = manager.getMigrationsForDatabase(elementClassName); + migrationDefinitionMap.keySet() + .stream().sorted((a, b) -> a > b ? -1 : 1) + .forEach(version -> { + migrationDefinitionMap.get(version).stream().sorted(Comparator.comparing(it -> it.priority)) + .forEach(migrationDefinition -> methodBuilder.addStatement("addMigration("+version+", new $T"+migrationDefinition.constructorName+")", migrationDefinition.elementClassName)); + }); + + } + builder.addMethod(methodBuilder.build()); + } + + private void writeGetters(TypeSpec.Builder typeBuilder) { + JavaPoetExtensions.overrideFun(typeBuilder, ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Object.class)), "getAssociatedDatabaseClassFile", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return $T.class", elementTypeName); + return null; + }); + + JavaPoetExtensions.overrideFun(typeBuilder, TypeName.BOOLEAN, "isForeignKeysSupported", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return " + foreignKeysSupported); + return null; + }); + + JavaPoetExtensions.overrideFun(typeBuilder, TypeName.BOOLEAN, "backupEnabled", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return " + backupEnabled); + return null; + }); + + JavaPoetExtensions.overrideFun(typeBuilder, TypeName.BOOLEAN, "areConsistencyChecksEnabled", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return " + consistencyChecksEnabled); + return null; + }); + + JavaPoetExtensions.overrideFun(typeBuilder, TypeName.INT, "getDatabaseVersion", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return " + databaseVersion); + return null; + }); + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/DatabaseHolderDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseHolderDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..a81efc56aed59ba1394b00368afb7803a2a10bb4 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseHolderDefinition.java @@ -0,0 +1,91 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.Handlers; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.utils.ElementExtensions; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Modifier; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Description: Top-level writer that handles writing all [DatabaseDefinition] + * and [com.dbflow5.annotation.TypeConverter] + */ +public class DatabaseHolderDefinition implements TypeDefinition { + public static final String OPTION_TARGET_MODULE_NAME = "targetModuleName"; + + private final ProcessorManager processorManager; + + public String className; + + public DatabaseHolderDefinition(ProcessorManager processorManager) { + this.processorManager = processorManager; + + String _className = ""; + Map options = this.processorManager.processingEnvironment.getOptions(); + if (options.containsKey(OPTION_TARGET_MODULE_NAME)) { + _className = options.get(OPTION_TARGET_MODULE_NAME) != null? options.get(OPTION_TARGET_MODULE_NAME) : ""; + } + + _className += ClassNames.DATABASE_HOLDER_STATIC_CLASS_NAME; + + className = _className; + } + + @Override + public TypeSpec typeSpec() { + TypeSpec.Builder builder = TypeSpec.classBuilder(this.className); + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.superclass(ClassNames.DATABASE_HOLDER); + + MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder(); + methodBuilder.addModifiers(Modifier.PUBLIC); + + processorManager.getTypeConverters().forEach(tc -> { + methodBuilder.addStatement("$L.put($T.class, new $T())", + Handlers.DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, + ElementExtensions.rawTypeName(tc.modelTypeName), + tc.className); + + tc.allowedSubTypes.forEach(subType -> { + methodBuilder.addStatement("$L.put($T.class, new $T())", + Handlers.DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, + ElementExtensions.rawTypeName(subType), tc.className); + }); + }); + + processorManager.getDatabaseHolderDefinitionList() + .stream().filter(Objects::nonNull) + .map(databaseObjectHolder -> { + if(databaseObjectHolder.databaseDefinition != null) { + return databaseObjectHolder.databaseDefinition.outputClassName; + } + return null; + }).sorted(Comparator.comparing(className1 -> className1 != null ? className1.simpleName() : null)) + .forEach(it -> methodBuilder.addStatement("new $T(this)", it)); + + builder.addMethod(methodBuilder.build()); + + return builder.build(); + } + + + /** + * If none of the database holder databases exist, don't generate a holder. + */ + public boolean isGarbage() { + List list = processorManager.getDatabaseHolderDefinitionList(); + for (DatabaseObjectHolder holder : list) { + if(holder == null) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/DatabaseObjectHolder.java b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseObjectHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..797dcc88e75a799ae6b1b9e6a256086422d4cd60 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/DatabaseObjectHolder.java @@ -0,0 +1,49 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.processor.definition.provider.ContentProviderDefinition; +import com.squareup.javapoet.TypeName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Description: Provides overarching holder for [DatabaseDefinition], [TableDefinition], + * and more. So we can safely use. + */ +public class DatabaseObjectHolder { + + public Map tableDefinitionMap = new HashMap<>(); + public Map tableNameMap = new HashMap<>(); + + public Map queryModelDefinitionMap = new HashMap<>(); + public Map modelViewDefinitionMap = new HashMap<>(); + public Map> manyToManyDefinitionMap = new HashMap<>(); + public Map providerMap = new HashMap<>(); + + public DatabaseDefinition databaseDefinition = null; + public void set(DatabaseDefinition databaseDefinition) { + this.databaseDefinition = databaseDefinition; + this.databaseDefinition.objectHolder = this; + } + + /** + * Retrieve what database class they're trying to reference. + */ + public List getMissingDBRefs() { + if (databaseDefinition == null) { + List list = new ArrayList<>(); + List finalList = list; + tableDefinitionMap.values().forEach(tableDefinition -> finalList.add("Database "+tableDefinition.associationalBehavior().databaseTypeName+" not found for Table "+tableDefinition.associationalBehavior().name)); + queryModelDefinitionMap.values().forEach(queryModelDefinition -> finalList.add("Database "+queryModelDefinition.associationalBehavior().databaseTypeName+" not found for QueryModel " + queryModelDefinition.elementName)); + modelViewDefinitionMap.values().forEach(modelViewDefinition -> finalList.add("Database "+modelViewDefinition.associationalBehavior().databaseTypeName+" not found for ModelView "+modelViewDefinition.elementName)); + providerMap.values().forEach(contentProviderDefinition -> finalList.add("Database "+contentProviderDefinition.databaseTypeName+" not found for ContentProvider "+contentProviderDefinition.elementName)); + + return finalList; + + } else { + return new ArrayList<>(); + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/EntityDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/EntityDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..b200ce1a01c39e6a930c5d9faa5c1427ded83431 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/EntityDefinition.java @@ -0,0 +1,232 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.definition.behavior.ColumnBehaviors; +import com.dbflow5.processor.definition.column.ColumnAccessor; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.definition.column.ReferenceColumnDefinition; +import com.dbflow5.processor.utils.*; +import com.squareup.javapoet.*; + +import java.io.IOException; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.processing.FilerException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +/** + * Description: Used to write Models and ModelViews + */ +public abstract class EntityDefinition extends BaseDefinition { + + public List columnDefinitions = new ArrayList<>(); + + public List sqlColumnDefinitions() { + return columnDefinitions.stream().filter(columnDefinition -> !(columnDefinition.type instanceof ColumnDefinition.Type.RowId)).collect(Collectors.toList()); + } + + public Map> associatedTypeConverters = new HashMap<>(); + public Map> globalTypeConverters = new HashMap<>(); + public List packagePrivateList = new ArrayList<>(); + + public Map classElementLookUpMap = new HashMap<>(); + + public String modelClassName() { + return typeElement.getSimpleName().toString(); + } + + public ColumnBehaviors.PrimaryKeyColumnBehavior primaryKeyColumnBehavior; + + public DatabaseDefinition databaseDefinition() { + DatabaseObjectHolder holder = manager.getDatabaseHolderDefinition(associationalBehavior().databaseTypeName); + if(holder != null) { + return holder.databaseDefinition; + } + throw new RuntimeException("DatabaseDefinition not found for DB element named ${associationalBehavior.name}" + + " for db type: ${associationalBehavior.databaseTypeName}."); + } + + public boolean hasGlobalTypeConverters() { + return !globalTypeConverters.isEmpty(); + } + + public boolean implementsLoadFromCursorListener() { + return ProcessorUtils.implementsClass(typeElement, manager.processingEnvironment, ClassNames.LOAD_FROM_CURSOR_LISTENER); + } + + public EntityDefinition(TypeElement typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager); + } + + public abstract Behaviors.AssociationalBehavior associationalBehavior(); + + public abstract Behaviors.CursorHandlingBehavior cursorHandlingBehavior(); + + public ColumnBehaviors.PrimaryKeyColumnBehavior primaryKeyColumnBehavior() { + if(primaryKeyColumnBehavior == null) { + primaryKeyColumnBehavior = new ColumnBehaviors.PrimaryKeyColumnBehavior(false, null, false); + } + return primaryKeyColumnBehavior; + } + + public abstract MethodDefinition[] methods(); + + protected abstract void createColumnDefinitions(TypeElement typeElement); + + public abstract List primaryColumnDefinitions(); + + public void prepareForWrite() { + classElementLookUpMap.clear(); + columnDefinitions.clear(); + packagePrivateList.clear(); + + prepareForWriteInternal(); + } + + protected abstract void prepareForWriteInternal(); + + public TypeName parameterClassName() { + return elementClassName; + } + + public String addColumnForCustomTypeConverter(ColumnDefinition columnDefinition, ClassName typeConverterName) { + List columnDefinitions = associatedTypeConverters.getOrDefault(typeConverterName, new ArrayList<>()); + columnDefinitions.add(columnDefinition); + return "typeConverter" + typeConverterName.simpleName(); + } + + public String addColumnForTypeConverter(ColumnDefinition columnDefinition, ClassName typeConverterName) { + List columnDefinitions = globalTypeConverters.getOrDefault(typeConverterName, new ArrayList<>()); + columnDefinitions.add(columnDefinition); + return "global_typeConverter" + typeConverterName.simpleName(); + } + + public void writeConstructor(TypeSpec.Builder builder) { + Methods.CustomTypeConverterPropertyMethod customTypeConverterPropertyMethod = new Methods.CustomTypeConverterPropertyMethod(this); + customTypeConverterPropertyMethod.addToType(builder); + + constructor(builder, builder1 -> { + if (hasGlobalTypeConverters()) { + builder1.addParameter(ParameterSpec.builder(ClassNames.DATABASE_HOLDER, "holder").build()); + } + builder1.addParameter(ParameterSpec.builder(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition").build()); + builder1.addModifiers(Modifier.PUBLIC); + builder1.addStatement("super(databaseDefinition)"); + CodeBlock.Builder codeBuilder = customTypeConverterPropertyMethod.addCode(CodeBlock.builder()); + builder1.addCode(codeBuilder.build()); + return builder1; + }); + } + + private TypeSpec.Builder constructor(TypeSpec.Builder builder, Function methodSpecFunction, ParameterSpec.Builder... parameters) { + List parameterSpecs = new ArrayList<>(); + if (parameters != null) { + for (ParameterSpec.Builder it : parameters) { + parameterSpecs.add(it.build()); + } + } + return builder.addMethod(methodSpecFunction.apply(MethodSpec.constructorBuilder()).addParameters(parameterSpecs).build()); + } + + public TypeSpec.Builder writeGetModelClass(TypeSpec.Builder typeBuilder, ClassName modelClassName) { + return JavaPoetExtensions.overrideFun(typeBuilder, ParameterizedTypeName.get(ClassName.get(Class.class), modelClassName), "getTable", new Function() { + @Override + public Void apply(MethodSpec.Builder builder) { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("$T.class", modelClassName); + return null; + } + }); + } + + public void writePackageHelper(ProcessingEnvironment processingEnvironment) throws IOException { + int count = 0; + + if (!packagePrivateList.isEmpty()) { + TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(elementClassName.simpleName() + "_Helper") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + for (ColumnDefinition columnDefinition : packagePrivateList) { + String helperClassName = ElementExtensions.getPackage(columnDefinition.element, ProcessorManager.manager) + "." + ElementExtensions.toClassName(columnDefinition.element.getEnclosingElement(), ProcessorManager.manager).simpleName() + "_Helper"; + if (columnDefinition instanceof ReferenceColumnDefinition) { + TableDefinition tableDefinition = databaseDefinition().objectHolder.tableDefinitionMap.get(((ReferenceColumnDefinition) columnDefinition).referencedClassName); + if (tableDefinition != null) { + helperClassName = ElementExtensions.getPackage(tableDefinition.element, ProcessorManager.manager) + "."+ClassName.get((TypeElement)tableDefinition.element).simpleName()+"_Helper"; + } + } + ClassName className = ElementUtility.getClassName(helperClassName, manager); + + if (className != null && ColumnAccessor.PackagePrivateScopeColumnAccessor.containsColumn(className, columnDefinition.columnName)) { + boolean samePackage = ElementUtility.isInSamePackage(manager, columnDefinition.element, element); + String methodName = StringUtils.capitalize(columnDefinition.columnName); + + MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("get" + methodName).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(columnDefinition.elementTypeName) + .addParameter(ParameterSpec.builder(elementTypeName, ModelUtils.variable).build()); + + if (samePackage) { + methodSpecBuilder.addStatement(ModelUtils.variable + "." + columnDefinition.elementName); + } else { + methodSpecBuilder.addStatement("$T.get" + methodName + "(" + ModelUtils.variable + ")", className); + } + + typeBuilder.addMethod(methodSpecBuilder.build()); + + MethodSpec.Builder methodSpecBuilder2 = MethodSpec.methodBuilder("set" + methodName).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(TypeName.VOID) + .addParameter(ParameterSpec.builder(elementTypeName, ModelUtils.variable).build()) + .addParameter(ParameterSpec.builder(columnDefinition.elementTypeName, "var").build()); + + if (samePackage) { + methodSpecBuilder2.addStatement(ModelUtils.variable + "." + columnDefinition.elementName + " = var"); + } else { + methodSpecBuilder2.addStatement("$T.set" + methodName + "(" + ModelUtils.variable + ", var)", className); + } + typeBuilder.addMethod(methodSpecBuilder2.build()); + + count++; + } else if (className == null) { + manager.logError(EntityDefinition.class, "Could not find classname for: " + helperClassName); + } + } + + // only write class if we have referenced fields. + if (count > 0) { + JavaFile.Builder javaFileBuilder = JavaFile.builder(packageName, typeBuilder.build()); + javaFileBuilder.build().writeTo(processingEnvironment.getFiler()); + } + } + } + + /** + * Do not support inheritance on package private fields without having ability to generate code for it in + * same package. + */ + public boolean checkInheritancePackagePrivate(boolean isPackagePrivateNotInSamePackage, Element element) { + if (isPackagePrivateNotInSamePackage && !manager.elementBelongsInTable(element)) { + manager.logError("Package private inheritance on non-table/querymodel/view " + + "is not supported without a @InheritedColumn annotation." + + " Make $element from ${element.enclosingElement} public or private."); + return true; + } + return false; + } + + public static void safeWritePackageHelper(Collection collection, ProcessorManager processorManager) throws IOException { + try { + for (EntityDefinition definition : collection) { + definition.writePackageHelper(processorManager.processingEnvironment); + } + } catch (FilerException e) { /*Ignored intentionally to allow multi-round table generation*/ + e.printStackTrace(); + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/IndexGroupsDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/IndexGroupsDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..34f29997ebf23af86bf65f6c23e53defa1ff548c --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/IndexGroupsDefinition.java @@ -0,0 +1,51 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.annotation.IndexGroup; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterizedTypeName; + +import javax.lang.model.element.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Description: + */ +public class IndexGroupsDefinition { + + private final TableDefinition tableDefinition; + + public String indexName; + public int indexNumber; + public boolean isUnique; + + public List columnDefinitionList = new ArrayList<>(); + + public IndexGroupsDefinition(TableDefinition tableDefinition, IndexGroup indexGroup) { + this.tableDefinition = tableDefinition; + indexName = indexGroup.name(); + indexNumber = indexGroup.number(); + isUnique = indexGroup.unique(); + } + + public FieldSpec fieldSpec() { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + codeBuilder.add("new $T<>(\""+indexName+"\", "+isUnique+", $T.class", com.dbflow5.processor.ClassNames.INDEX_PROPERTY, tableDefinition.elementTypeName); + if (!columnDefinitionList.isEmpty()) { + codeBuilder.add(","); + } + AtomicInteger index = new AtomicInteger(0); + columnDefinitionList.forEach(it -> it.appendIndexInitializer(codeBuilder, index)); + codeBuilder.add(")"); + + FieldSpec.Builder builder = FieldSpec.builder(ParameterizedTypeName.get(com.dbflow5.processor.ClassNames.INDEX_PROPERTY, tableDefinition.elementClassName), "index_"+indexName); + builder.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + builder.initializer(codeBuilder.build()); + return builder.build(); + } + +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/ManyToManyDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/ManyToManyDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..780183921a42586297f93a9d4aafba296d8a249b --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/ManyToManyDefinition.java @@ -0,0 +1,126 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.ForeignKey; +import com.dbflow5.annotation.ManyToMany; +import com.dbflow5.annotation.PrimaryKey; +import com.dbflow5.annotation.Table; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.utils.ElementExtensions; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +/** + * Description: Generates the Model class that is used in a many to many. + */ +public class ManyToManyDefinition extends BaseDefinition { + + public TypeName databaseTypeName; + + private final TypeName referencedTable; + private final boolean generateAutoIncrement; + private final boolean sameTableReferenced; + private final String generatedTableClassName; + private final boolean saveForeignKeyModels; + private final String thisColumnName; + private final String referencedColumnName; + + public ManyToManyDefinition(TypeElement element, ProcessorManager processorManager, ManyToMany manyToMany) { + super(element, processorManager); + + databaseTypeName = ProcessorUtils.extractTypeNameFromAnnotation(element, Table.class, table -> { + table.database(); + return null; + }); + + referencedTable = ProcessorUtils.extractTypeNameFromAnnotation(manyToMany, e -> null, manyToMany1 -> { + manyToMany1.referencedTable(); + return null; + }); + + generateAutoIncrement = manyToMany.generateAutoIncrement(); + sameTableReferenced = referencedTable == elementTypeName; + generatedTableClassName = manyToMany.generatedTableClassName(); + saveForeignKeyModels = manyToMany.saveForeignKeyModels(); + thisColumnName = manyToMany.thisTableColumnName(); + referencedColumnName = manyToMany.referencedTableColumnName(); + + if (!com.dbflow5.processor.utils.StringUtils.isNullOrEmpty(thisColumnName) && !com.dbflow5.processor.utils.StringUtils.isNullOrEmpty(referencedColumnName) && thisColumnName.equals(referencedColumnName)) { + manager.logError(ManyToManyDefinition.class, "The thisTableColumnName and referenceTableColumnName cannot be the same"); + } + } + + public void prepareForWrite() { + if (!generatedTableClassName.isEmpty()) { + ClassName referencedOutput = ElementExtensions.toClassName(ElementExtensions.toTypeElement(referencedTable, manager), manager); + setOutputClassName("_" + referencedOutput.simpleName()); + } else { + setOutputClassNameFull(generatedTableClassName); + } + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + typeBuilder.addAnnotation(AnnotationSpec.builder(Table.class) + .addMember("database", "$T.class", databaseTypeName).build()); + + TableDefinition referencedDefinition = manager.getTableDefinition(databaseTypeName, referencedTable); + TableDefinition selfDefinition = manager.getTableDefinition(databaseTypeName, elementTypeName); + + if (generateAutoIncrement) { + AnnotationSpec.Builder annnotationBuilder = AnnotationSpec.builder(PrimaryKey.class).addMember("autoincrement", CodeBlock.of("true")); + typeBuilder.addField(FieldSpec.builder(TypeName.LONG, "_id").addAnnotation(annnotationBuilder.build()).build()); + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getId").addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(TypeName.LONG).addStatement("return _id"); + typeBuilder.addMethod(methodBuilder.build()); + } + + if(referencedDefinition != null) { + appendColumnDefinitions(typeBuilder, referencedDefinition, 0, referencedColumnName); + } + + if(selfDefinition != null) { + appendColumnDefinitions(typeBuilder, selfDefinition, 1, thisColumnName); + } + } + + @Override + public TypeName extendsClass() { + return ClassNames.BASE_MODEL; + } + + private void appendColumnDefinitions(TypeSpec.Builder typeBuilder, TableDefinition referencedDefinition, int index, String optionalName) { + String fieldName = referencedDefinition.elementName.toLowerCase(); + if (sameTableReferenced) { + fieldName += index; + } + // override with the name (if specified) + if (optionalName.isEmpty()) { + fieldName = optionalName; + } + + FieldSpec.Builder fieldBuilder = FieldSpec.builder(referencedDefinition.elementClassName, fieldName); + if(!generateAutoIncrement) { + fieldBuilder.addAnnotation(AnnotationSpec.builder(PrimaryKey.class).build()); + } + fieldBuilder.addAnnotation(AnnotationSpec.builder(ForeignKey.class).addMember("saveForeignKeyModel", CodeBlock.of(String.valueOf(saveForeignKeyModels))).build()); + typeBuilder.addField(fieldBuilder.build()); + + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("get" + StringUtils.capitalize(fieldName)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(referencedDefinition.elementClassName) + .addStatement("return " + fieldName); + typeBuilder.addMethod(methodBuilder.build()); + + MethodSpec.Builder methodBuilder2 = MethodSpec.methodBuilder("set" + StringUtils.capitalize(fieldName)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(referencedDefinition.elementClassName, "param") + .addStatement(fieldName + " = param"); + typeBuilder.addMethod(methodBuilder2.build()); + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/Methods.java b/processor/src/main/java/com/dbflow5/processor/definition/Methods.java new file mode 100644 index 0000000000000000000000000000000000000000..e2461308770db4e7c025ff2fe451e92f29d5ebd8 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/Methods.java @@ -0,0 +1,804 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.column.ColumnAccessCombiner; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.utils.CodeExtensions; +import com.dbflow5.processor.utils.JavaPoetExtensions; +import com.dbflow5.processor.utils.ModelUtils; +import com.squareup.javapoet.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import javax.lang.model.element.Modifier; + +/** + * Description: + * + * @author Andrew Grosner (fuzz) + */ +public class Methods{ + + /** + * Description: Writes the bind to content values method in the ModelDAO. + */ + public static class BindToContentValuesMethod implements MethodDefinition { + public static final String PARAM_CONTENT_VALUES = "values"; + + private final EntityDefinition entityDefinition; + private final boolean isInsert; + private final boolean implementsContentValuesListener; + + public BindToContentValuesMethod(EntityDefinition entityDefinition, boolean isInsert, boolean implementsContentValuesListener) { + this.entityDefinition = entityDefinition; + this.isInsert = isInsert; + this.implementsContentValuesListener = implementsContentValuesListener; + } + + public MethodSpec methodSpec() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(isInsert? "bindToInsertValues" : "bindToContentValues") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.CONTENT_VALUES, PARAM_CONTENT_VALUES) + .addParameter(entityDefinition.parameterClassName(), ModelUtils.variable) + .returns(TypeName.VOID); + + MethodSpec.Builder retMethodBuilder = methodBuilder; + + if (isInsert) { + entityDefinition.columnDefinitions.forEach(columnDefinition -> { + if (!(columnDefinition.type instanceof ColumnDefinition.Type.PrimaryAutoIncrement) + && !(columnDefinition.type instanceof ColumnDefinition.Type.RowId)) { + methodBuilder.addCode(columnDefinition.contentValuesStatement()); + } + }); + + if (implementsContentValuesListener) { + methodBuilder.addStatement("$L.onBindTo$LValues($L)", + ModelUtils.variable, isInsert? "Insert" : "Content", PARAM_CONTENT_VALUES); + } + } else { + if (entityDefinition.primaryKeyColumnBehavior().hasAutoIncrement + || entityDefinition.primaryKeyColumnBehavior().hasRowID) { + ColumnDefinition autoIncrement = entityDefinition.primaryKeyColumnBehavior().associatedColumn; + if(autoIncrement != null){ + methodBuilder.addCode(autoIncrement.contentValuesStatement()); + } + } else if (!implementsContentValuesListener) { + retMethodBuilder = null; + } + + methodBuilder.addStatement("bindToInsertValues($L, $L)", PARAM_CONTENT_VALUES, ModelUtils.variable); + if (implementsContentValuesListener) { + methodBuilder.addStatement("$L.onBindTo$LValues($L)", + ModelUtils.variable, isInsert? "Insert" : "Content", PARAM_CONTENT_VALUES); + } + } + + return retMethodBuilder != null? retMethodBuilder.build() : null; + } + } + + /** + * Description: + */ + public static class BindToStatementMethod implements MethodDefinition { + public static final String PARAM_STATEMENT = "statement"; + + private final TableDefinition tableDefinition; + private final Mode mode; + + public BindToStatementMethod(TableDefinition tableDefinition, Mode mode) { + this.tableDefinition = tableDefinition; + this.mode = mode; + } + + public enum Mode { + INSERT { + @Override + String methodName() { + return "bindToInsertStatement"; + } + + @Override + String sqlListenerName() { + return "onBindToInsertStatement"; + } + }, + UPDATE { + @Override + String methodName() { + return "bindToUpdateStatement"; + } + + @Override + String sqlListenerName() { + return "onBindToUpdateStatement"; + } + }, + DELETE { + @Override + String methodName() { + return "bindToDeleteStatement"; + } + + @Override + String sqlListenerName() { + return "onBindToDeleteStatement"; + } + }; + + abstract String methodName(); + + abstract String sqlListenerName(); + } + + @Override + public MethodSpec methodSpec() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(mode.methodName()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.DATABASE_STATEMENT, PARAM_STATEMENT) + .addParameter(tableDefinition.parameterClassName(), + ModelUtils.variable).returns(TypeName.VOID); + + // attach non rowid first, then go onto the WHERE clause + if(mode == Mode.INSERT) { + AtomicInteger start = new AtomicInteger(1); + tableDefinition.sqlColumnDefinitions().forEach(columnDefinition -> { + methodBuilder.addCode(columnDefinition.getSQLiteStatementMethod(start, true)); + start.incrementAndGet(); + }); + }else if(mode == Mode.UPDATE) { + AtomicInteger realCount = new AtomicInteger(1); + // attach non rowid first, then go onto the WHERE clause + tableDefinition.sqlColumnDefinitions().forEach(columnDefinition -> { + methodBuilder.addCode(columnDefinition.getSQLiteStatementMethod(realCount, true)); + realCount.incrementAndGet(); + }); + tableDefinition.primaryColumnDefinitions().forEach(columnDefinition -> { + methodBuilder.addCode(columnDefinition.getSQLiteStatementMethod(realCount,false)); + realCount.incrementAndGet(); + }); + }else if(mode == Mode.DELETE) { + AtomicInteger realCount2 = new AtomicInteger(1); + tableDefinition.primaryColumnDefinitions().forEach(columnDefinition -> { + methodBuilder.addCode(columnDefinition.getSQLiteStatementMethod(realCount2, true)); + realCount2.incrementAndGet(); + }); + } + + if (tableDefinition.implementsSqlStatementListener) { + methodBuilder.addStatement(ModelUtils.variable + "."+mode.sqlListenerName()+"("+PARAM_STATEMENT+")"); + } + + return methodBuilder.build(); + } + } + + /** + * Description: + */ + public static class CreationQueryMethod implements MethodDefinition { + private final TableDefinition tableDefinition; + + public CreationQueryMethod(TableDefinition tableDefinition) { + this.tableDefinition = tableDefinition; + } + + @Override + public MethodSpec methodSpec() { + return JavaPoetExtensions.overrideFun(String.class, "getCreationQuery", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + if (tableDefinition.type.isVirtual()) { + CodeBlock codeBlock = CodeExtensions.codeBlock(builder1 -> { + builder1.add("CREATE VIRTUAL TABLE IF NOT EXISTS " + StringUtils.quote(tableDefinition.associationalBehavior().name) + " USING "); + if (tableDefinition.type == TableDefinition.Type.FTS4) { + builder1.add("FTS4"); + } else if (tableDefinition.type == TableDefinition.Type.FTS3) { + builder1.add("FTS3"); + } else { + ProcessorManager.manager.logError("Invalid table type found " + tableDefinition.type); + } + + builder1.add("("); + // FTS4 uses column names directly. + StringBuilder sb = new StringBuilder(); + tableDefinition.columnDefinitions.forEach(columnDefinition -> sb.append(StringUtils.quote(columnDefinition.columnName))); + builder1.add(sb.toString()); + tableDefinition.ftsBehavior.addContentTableCode(!tableDefinition.columnDefinitions.isEmpty(), builder1); + builder1.add(")"); + return builder1; + }); + String S = "\"" + codeBlock + "\""; + builder.addCode("return " + S + ";\\n"); + } else { + int foreignSize = tableDefinition.foreignKeyDefinitions.size(); + + CodeBlock creationBuilder = CodeExtensions.codeBlock(builder1 -> { + builder1.add("CREATE " + (tableDefinition.temporary ? "TEMP " : "") + "TABLE IF NOT EXISTS " + StringUtils.quote(tableDefinition.associationalBehavior().name) + "("); + + StringBuilder sb = new StringBuilder(); + tableDefinition.columnDefinitions.forEach(columnDefinition -> sb.append(columnDefinition.creationName().toString())); + + builder1.add(sb.toString()); + tableDefinition.uniqueGroupsDefinitions.forEach(uniqueGroupsDefinition -> { + if (!uniqueGroupsDefinition.columnDefinitionList.isEmpty()) { + builder1.add(uniqueGroupsDefinition.creationName()); + } + }); + + if (!tableDefinition.primaryKeyColumnBehavior().hasAutoIncrement) { + int primarySize = tableDefinition.primaryColumnDefinitions().size(); + if (primarySize > 0) { + StringBuilder sb2 = new StringBuilder(); + tableDefinition.primaryColumnDefinitions().forEach(columnDefinition -> { + sb2.append(columnDefinition.primaryKeyName()); + }); + builder1.add(", PRIMARY KEY(" + sb2.toString() + ")"); + if (!tableDefinition.primaryKeyConflictActionName.isEmpty()) { + builder1.add(" ON CONFLICT " + tableDefinition.primaryKeyConflictActionName); + } + } + } + if (foreignSize == 0) { + builder1.add(")"); + } + return builder1; + }); + + CodeBlock.Builder codeBuilder = CodeBlock.builder().add("return \"" + creationBuilder); + + tableDefinition.foreignKeyDefinitions.forEach(fk -> { + EntityDefinition referencedTableDefinition = ProcessorManager.manager.getReferenceDefinition(tableDefinition.associationalBehavior().databaseTypeName, fk.referencedClassName); + if (referencedTableDefinition == null) { + fk.throwCannotFindReference(); + } else { + StringBuilder buildString = new StringBuilder(); + buildString.append(", FOREIGN KEY("); + buildString.append(StringUtils.joinToString(fk.referenceDefinitionList(), ", ", referenceDefinition -> StringUtils.quote(referenceDefinition.columnName()))); + buildString.append(") REFERENCES "); + buildString.append(referencedTableDefinition.associationalBehavior().name + " "); + buildString.append("("); + buildString.append(StringUtils.joinToString(fk.referenceDefinitionList(), ", ", referenceDefinition -> StringUtils.quote(referenceDefinition.foreignColumnName))); + buildString.append(") ON UPDATE " + fk.foreignKeyColumnBehavior.onUpdate.name().replace("_", " ")); + buildString.append(" ON DELETE " + fk.foreignKeyColumnBehavior.onDelete.name().replace("_", " ")); + if (fk.foreignKeyColumnBehavior.deferred) { + buildString.append(" DEFERRABLE INITIALLY DEFERRED"); + } + + codeBuilder.add(buildString.toString()); + } + }); + + tableDefinition.foreignKeyDefinitions.forEach(fk -> { + EntityDefinition referencedTableDefinition = ProcessorManager.manager.getReferenceDefinition(tableDefinition.associationalBehavior().databaseTypeName, fk.referencedClassName); + if (referencedTableDefinition == null) { + fk.throwCannotFindReference(); + } else { + StringBuilder buildString = new StringBuilder(); + buildString.append(", FOREIGN KEY("); + buildString.append(StringUtils.joinToString(fk.referenceDefinitionList(), ", ", referenceDefinition -> StringUtils.quote(referenceDefinition.columnName()))); + buildString.append(") REFERENCES "); + buildString.append(referencedTableDefinition.associationalBehavior().name + " "); + buildString.append("("); + buildString.append(StringUtils.joinToString(fk.referenceDefinitionList(), ", ", referenceDefinition -> StringUtils.quote(referenceDefinition.foreignColumnName))); + buildString.append(") ON UPDATE " + fk.foreignKeyColumnBehavior.onUpdate.name().replace("_", " ")); + buildString.append(" ON DELETE $" + fk.foreignKeyColumnBehavior.onDelete.name().replace("_", " ")); + if (fk.foreignKeyColumnBehavior.deferred) { + buildString.append(" DEFERRABLE INITIALLY DEFERRED"); + } + + codeBuilder.add(buildString.toString()); + } + }); + + if (foreignSize > 0) { + codeBuilder.add(")"); + } + codeBuilder.add("\";\n"); + + builder.addCode(codeBuilder.build()); + } + return null; + }); + } + } + + /** + * Description: Writes out the custom type converter fields. + */ + public static class CustomTypeConverterPropertyMethod implements Adders.TypeAdder, Adders.CodeAdder { + private final EntityDefinition entityDefinition; + + public CustomTypeConverterPropertyMethod(EntityDefinition entityDefinition) { + this.entityDefinition = entityDefinition; + } + + @Override + public void addToType(TypeSpec.Builder typeBuilder) { + Set customTypeConverters = entityDefinition.associatedTypeConverters.keySet(); + customTypeConverters.forEach(className -> { + FieldSpec.Builder builder = FieldSpec.builder(className, "typeConverter" + className.simpleName()).addModifiers(Modifier.PRIVATE, Modifier.FINAL); + builder.initializer(CodeBlock.builder().add("new $T()", className).build()); + typeBuilder.addField(builder.build()); + }); + + Set globalTypeConverters = entityDefinition.globalTypeConverters.keySet(); + globalTypeConverters.forEach(className -> { + FieldSpec.Builder builder = FieldSpec.builder(className, "global_typeConverter" + className.simpleName()).addModifiers(Modifier.PRIVATE, Modifier.FINAL); + typeBuilder.addField(builder.build()); + }); + } + + @Override + public CodeBlock.Builder addCode(CodeBlock.Builder code) { + // Constructor code + Set globalTypeConverters = entityDefinition.globalTypeConverters.keySet(); + globalTypeConverters.forEach(className -> { + List def = entityDefinition.globalTypeConverters.get(className); + if(def != null) { + ColumnDefinition firstDef = def.get(0); + if(firstDef != null) { + List typeNames = firstDef.typeConverterElementNames(); + typeNames.forEach(typeName -> code.addStatement("global_typeConverter "+ className.simpleName() + + "= ($T) holder.getTypeConverterForClass($T.class)", className, typeName)); + } + } + }); + return code; + } + } + + /** + * Description: + */ + public static class ExistenceMethod implements MethodDefinition { + private final EntityDefinition tableDefinition; + + public ExistenceMethod(EntityDefinition tableDefinition) { + this.tableDefinition = tableDefinition; + } + + @Override + public MethodSpec methodSpec() { + if (!tableDefinition.primaryColumnDefinitions().isEmpty()) { + ColumnDefinition primaryColumn = tableDefinition.primaryKeyColumnBehavior().associatedColumn; + if(primaryColumn == null) { + primaryColumn = tableDefinition.primaryColumnDefinitions().get(0); + } + + if (primaryColumn.shouldWriteExistence()) { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + primaryColumn.appendExistenceMethod(codeBuilder); + + return MethodSpec + .methodBuilder("exists") + .returns(TypeName.BOOLEAN) + .addParameter(ParameterSpec.builder(tableDefinition.parameterClassName(), ModelUtils.variable).build()) + .addParameter(ParameterSpec.builder(ClassNames.DATABASE_WRAPPER, "wrapper").build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(codeBuilder.build()) + .build(); + } + } + return null; + } + } + + /** + * Description: + */ + public static class InsertStatementQueryMethod implements MethodDefinition { + + private final TableDefinition tableDefinition; + private final Mode mode; + + public InsertStatementQueryMethod(TableDefinition tableDefinition, Mode mode) { + this.tableDefinition = tableDefinition; + this.mode = mode; + } + + public enum Mode { + INSERT, + SAVE + } + + @Override + public MethodSpec methodSpec() { + String name; + switch (mode){ + case INSERT: + name = "getInsertStatementQuery"; + break; + case SAVE: + name = "getSaveStatementQuery"; + break; + default: + name = ""; + break; + } + + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + codeBuilder.add("INSERT "); + if (mode != Mode.SAVE) { + if (!tableDefinition.insertConflictActionName.isEmpty()) { + codeBuilder.add("OR "+tableDefinition.insertConflictActionName+" "); + } + } else { + codeBuilder.add("OR "+ConflictAction.REPLACE+" "); + } + codeBuilder.add("INTO "+StringUtils.quote(tableDefinition.associationalBehavior().name)+"("); + + List columnDefinitions = tableDefinition.sqlColumnDefinitions(); + for(int index=0;index 0) codeBuilder.add(","); + codeBuilder.add(columnDefinition.insertStatementColumnName()); + } + + codeBuilder.add(") VALUES ("); + + for(int index=0;index 0) codeBuilder.add(","); + codeBuilder.add(columnDefinition.insertStatementValuesString()); + } + + codeBuilder.add(")"); + + return MethodSpec + .methodBuilder(name) + .returns(String.class) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return \"" + codeBuilder.build() + "\"") + .build(); + } + } + + public static class UpdateStatementQueryMethod implements MethodDefinition { + private final TableDefinition tableDefinition; + + public UpdateStatementQueryMethod(TableDefinition tableDefinition) { + this.tableDefinition = tableDefinition; + } + + @Override + public MethodSpec methodSpec() { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + codeBuilder.add("UPDATE"); + if (!tableDefinition.updateConflictActionName.isEmpty()) { + codeBuilder.add(" OR " + tableDefinition.updateConflictActionName); + } + codeBuilder.add(" "+StringUtils.quote(tableDefinition.associationalBehavior().name)+" SET "); + + // can only change non primary key values. + List columnDefinitions = tableDefinition.sqlColumnDefinitions(); + for(int index=0;index 0) codeBuilder.add(","); + codeBuilder.add(columnDefinition.updateStatementBlock()); + } + + codeBuilder.add(" WHERE "); + + // primary key values used as WHERE + columnDefinitions = tableDefinition.columnDefinitions.stream().filter(columnDefinition -> columnDefinition.type.isPrimaryField() || tableDefinition.type.isVirtual()).collect(Collectors.toList()); + for(int index=0;index 0) codeBuilder.add(" AND "); + codeBuilder.add(columnDefinition.updateStatementBlock()); + } + + return MethodSpec + .methodBuilder("getUpdateStatementQuery") + .returns(String.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(Override.class) + .addStatement("return \"" + codeBuilder.build() + "\"") + .build(); + + } + } + + public static class DeleteStatementQueryMethod implements MethodDefinition { + private final TableDefinition tableDefinition; + + public DeleteStatementQueryMethod(TableDefinition tableDefinition) { + this.tableDefinition = tableDefinition; + } + + @Override + public MethodSpec methodSpec() { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + codeBuilder.add("DELETE FROM "+StringUtils.quote(tableDefinition.associationalBehavior().name)+" WHERE "); + + // primary key values used as WHERE + List columnDefinitions = tableDefinition.columnDefinitions.stream().filter(columnDefinition -> columnDefinition.type.isPrimaryField() || tableDefinition.type.isVirtual()).collect(Collectors.toList()); + + for(int index=0;index 0) codeBuilder.add(" AND "); + codeBuilder.add(columnDefinition.updateStatementBlock()); + } + + return MethodSpec + .methodBuilder("getDeleteStatementQuery") + .returns(String.class) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return \"" + codeBuilder + "\"") + .build(); + } + } + + /** + * Description: + */ + public static class LoadFromCursorMethod implements MethodDefinition { + public static final String PARAM_CURSOR = "cursor"; + private final EntityDefinition entityDefinition; + + public LoadFromCursorMethod(EntityDefinition entityDefinition) { + this.entityDefinition = entityDefinition; + } + + @Override + public MethodSpec methodSpec() { + MethodSpec.Builder methodBuilder = MethodSpec + .methodBuilder("loadFromCursor") + .returns(entityDefinition.parameterClassName()) + .addParameter(ParameterSpec.builder(ClassNames.FLOW_CURSOR, PARAM_CURSOR).build()) + .addParameter(ParameterSpec.builder(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("$1T "+ModelUtils.variable+" = new $1T()", entityDefinition.parameterClassName()); + + AtomicInteger index = new AtomicInteger(0); + NameAllocator nameAllocator = new NameAllocator(); // unique names + entityDefinition.columnDefinitions.forEach(columnDefinition -> { + methodBuilder.addCode(columnDefinition.getLoadFromCursorMethod(true, index, nameAllocator)); + index.incrementAndGet(); + }); + + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + if (entityDefinition instanceof TableDefinition) { + ((TableDefinition) entityDefinition).oneToManyDefinitions.stream() + .filter(OneToManyDefinition::isLoad) + .forEach(oneToManyDefinition -> oneToManyDefinition.writeLoad(codeBuilder)); + } + methodBuilder.addCode(codeBuilder.build()); + + if (entityDefinition.implementsLoadFromCursorListener()) { + methodBuilder.addStatement(ModelUtils.variable + ".onLoadFromCursor("+PARAM_CURSOR+")"); + } + + methodBuilder.addStatement("return "+ModelUtils.variable); + + return methodBuilder.build(); + } + } + + /** + * Description: + */ + public static class OneToManyDeleteMethod implements MethodDefinition { + private final TableDefinition tableDefinition; + private final boolean isPlural; + + private final String variableName; + private final TypeName typeName ; + private final String methodName; + + public OneToManyDeleteMethod(TableDefinition tableDefinition, boolean isPlural) { + this.tableDefinition = tableDefinition; + this.isPlural = isPlural; + + variableName = isPlural? "models" : ModelUtils.variable; + typeName = !isPlural? tableDefinition.elementClassName : + ParameterizedTypeName.get(ClassName.get(Collection.class), WildcardTypeName.subtypeOf(tableDefinition.elementClassName)); + + methodName = "delete" + (isPlural? "All" : ""); + } + + @Override + public MethodSpec methodSpec() { + boolean shouldWrite = tableDefinition.oneToManyDefinitions.stream().anyMatch(OneToManyDefinition::isDelete); + if (shouldWrite || tableDefinition.cachingBehavior().cachingEnabled) { + TypeName returnTypeName = isPlural? TypeName.LONG : TypeName.BOOLEAN; + + MethodSpec.Builder methodBuilder = MethodSpec + .methodBuilder(methodName) + .returns(returnTypeName) + .addParameter(typeName, variableName) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper); + + if (tableDefinition.cachingBehavior().cachingEnabled) { + methodBuilder.addStatement("cacheAdapter.removeModel"+(isPlural? "s" : "")+"FromCache("+variableName+")"); + } + + methodBuilder.addStatement("$T successful = super."+methodName+"("+variableName+""+ ColumnAccessCombiner.wrapperCommaIfBaseModel(true)+")", returnTypeName); + + if (isPlural && !tableDefinition.oneToManyDefinitions.isEmpty()) { + methodBuilder.beginControlFlow("for " + " ("+"$T model: models"+")", tableDefinition.elementClassName); + tableDefinition.oneToManyDefinitions.forEach(oneToManyDefinition -> oneToManyDefinition.writeDelete(methodBuilder)); + methodBuilder.endControlFlow(); + } else { + tableDefinition.oneToManyDefinitions.forEach(oneToManyDefinition -> oneToManyDefinition.writeDelete(methodBuilder)); + } + + methodBuilder.addStatement("return successful"); + + return methodBuilder.build(); + } + return null; + } + } + + /** + * Description: Overrides the save, update, and insert methods if the [com.dbflow5.annotation.OneToMany.Method.SAVE] is used. + */ + public static class OneToManySaveMethod implements MethodDefinition { + public static final String METHOD_SAVE = "save"; + public static final String METHOD_UPDATE = "update"; + public static final String METHOD_INSERT = "insert"; + + private final TableDefinition tableDefinition; + private final String methodName; + private final boolean isPlural; + + private final String variableName; + private final TypeName typeName; + + private final String fullMethodName; + + public OneToManySaveMethod(TableDefinition tableDefinition, String methodName, boolean isPlural) { + this.tableDefinition = tableDefinition; + this.methodName = methodName; + this.isPlural = isPlural; + + variableName = isPlural? "models" : ModelUtils.variable; + typeName = !isPlural? tableDefinition.elementClassName : + ParameterizedTypeName.get(ClassName.get(Collection.class), WildcardTypeName.subtypeOf(tableDefinition.elementClassName)); + + fullMethodName = methodName + (isPlural? "All" : ""); + } + + @Override + public MethodSpec methodSpec() { + if (!tableDefinition.oneToManyDefinitions.isEmpty() || tableDefinition.cachingBehavior().cachingEnabled) { + TypeName retType = TypeName.BOOLEAN; + String retStatement = "successful"; + if (isPlural) { + retType = ClassName.LONG; + retStatement = "count"; + } else if (fullMethodName.equals(METHOD_INSERT)) { + retType = ClassName.LONG; + retStatement = "rowId"; + } + + MethodSpec.Builder methodBuilder = MethodSpec + .methodBuilder(fullMethodName) + .returns(retType) + .addParameter(ParameterSpec.builder(typeName, variableName).build()) + .addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + if (isPlural) { + codeBuilder.add("long count = "); + } else if (fullMethodName.equals(METHOD_INSERT)) { + codeBuilder.add("long rowId = "); + } else if (fullMethodName.equals(METHOD_UPDATE) || fullMethodName == METHOD_SAVE) { + codeBuilder.add("boolean successful = "); + } + codeBuilder.addStatement("super."+fullMethodName+"("+variableName+""+ColumnAccessCombiner.wrapperCommaIfBaseModel(true)+")"); + + if (tableDefinition.cachingBehavior().cachingEnabled) { + codeBuilder.addStatement("cacheAdapter.storeModel"+(isPlural? "s" : "")+"InCache("+variableName+")"); + } + + methodBuilder.addCode(codeBuilder.build()); + + List filteredDefinitions = new ArrayList<>(); + + tableDefinition.oneToManyDefinitions.forEach(oneToManyDefinition -> { + if(oneToManyDefinition.isSave()) { + filteredDefinitions.add(oneToManyDefinition); + } + }); + + if (isPlural && !filteredDefinitions.isEmpty()) { + methodBuilder.beginControlFlow("for ($T model: models)", tableDefinition.elementClassName); + filteredDefinitions.forEach(oneToManyDefinition -> { + switch (methodName){ + case METHOD_SAVE: + oneToManyDefinition.writeSave(methodBuilder); + break; + case METHOD_UPDATE: + oneToManyDefinition.writeUpdate(methodBuilder); + break; + case METHOD_INSERT: + oneToManyDefinition.writeInsert(methodBuilder); + break; + default: + break; + } + }); + methodBuilder.endControlFlow(); + } else { + filteredDefinitions.forEach(oneToManyDefinition -> { + switch (methodName){ + case METHOD_SAVE: + oneToManyDefinition.writeSave(methodBuilder); + break; + case METHOD_UPDATE: + oneToManyDefinition.writeUpdate(methodBuilder); + break; + case METHOD_INSERT: + oneToManyDefinition.writeInsert(methodBuilder); + break; + default: + break; + } + }); + } + + methodBuilder.addStatement("return " + retStatement); + + return methodBuilder.build(); + } else { + return null; + } + } + } + + /** + * Description: Creates a method that builds a clause of ConditionGroup that represents its primary keys. Useful + * for updates or deletes. + */ + public static class PrimaryConditionMethod implements MethodDefinition { + private EntityDefinition tableDefinition; + + public PrimaryConditionMethod(EntityDefinition tableDefinition) { + this.tableDefinition = tableDefinition; + } + + @Override + public MethodSpec methodSpec() { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + codeBuilder.addStatement("$T clause = $T.clause()", ClassNames.OPERATOR_GROUP, ClassNames.OPERATOR_GROUP); + tableDefinition.primaryColumnDefinitions().forEach(columnDefinition -> { + CodeBlock.Builder code = CodeBlock.builder(); + columnDefinition.appendPropertyComparisonAccessStatement(code); + codeBuilder.add(code.build()); + }); + + return MethodSpec + .methodBuilder("getPrimaryConditionClause") + .returns(ClassNames.OPERATOR_GROUP) + .addParameter(ParameterSpec.builder(tableDefinition.parameterClassName(), ModelUtils.variable).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(codeBuilder.build()) + .addStatement("return clause") + .build(); + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/MigrationDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/MigrationDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..9ae93759288bc475dda1bea7136d0d175f36fb35 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/MigrationDefinition.java @@ -0,0 +1,66 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.annotation.Migration; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import java.util.List; + +/** + * Description: Used in holding data about migration files. + */ +public class MigrationDefinition extends BaseDefinition { + public String constructorName; + + public TypeName databaseName; + public int version; + public int priority; + + public MigrationDefinition(Migration migration, ProcessorManager processorManager, TypeElement typeElement) { + super(typeElement, processorManager); + + setOutputClassName(""); + databaseName = ProcessorUtils.extractTypeNameFromAnnotation(migration, e -> null, migration1 -> { + migration1.database(); + return null; + }); + + version = migration.version(); + priority = migration.priority(); + + List elements = typeElement.getEnclosedElements(); + + elements.forEach(element -> { + if (element instanceof ExecutableElement && element.getSimpleName().toString().equals("")) { + if (constructorName.isEmpty()) { + manager.logError(MigrationDefinition.class, "Migrations cannot have more than one constructor. " + + "They can only have an Empty() or single-parameter constructor Empty(Empty.class) that specifies " + + "the .class of this migration class."); + } + + if (((ExecutableElement) element).getParameters().isEmpty()) { + constructorName = "()"; + } else if (((ExecutableElement) element).getParameters().size() == 1) { + List params = ((ExecutableElement) element).getParameters(); + VariableElement param = params.get(0); + + TypeName type = TypeName.get(param.asType()); + if (type instanceof ParameterizedTypeName && ((ParameterizedTypeName) type).rawType == ClassName.get(Class.class)) { + TypeName containedType = ((ParameterizedTypeName) type).typeArguments.get(0); + constructorName = CodeBlock.of("($T.class)", containedType).toString(); + } else { + manager.logError(MigrationDefinition.class, "Wrong parameter type found for $typeElement. Found $type but required ModelClass.class"); + } + } + } + }); + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/ModelViewDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/ModelViewDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..73effcec2a00a4c28deeac1f9dd5706f8ab4707e --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/ModelViewDefinition.java @@ -0,0 +1,180 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ColumnMap; +import com.dbflow5.annotation.ModelView; +import com.dbflow5.annotation.ModelViewQuery; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.Validators; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.definition.behavior.CreationQueryBehavior; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.utils.ElementExtensions; +import com.dbflow5.processor.utils.ElementUtility; +import com.dbflow5.processor.utils.JavaPoetExtensions; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import java.util.List; + +/** + * Description: Used in writing ModelViewAdapters + */ +public class ModelViewDefinition extends EntityDefinition { + + private final ModelView modelView; + private String queryFieldName = null; + + public ModelViewDefinition(ModelView modelView, ProcessorManager manager, TypeElement element) { + super(element, manager); + this.modelView = modelView; + priority = modelView.priority(); + creationQueryBehavior = new CreationQueryBehavior(modelView.createWithDatabase()); + + setOutputClassName("_ViewTable"); + } + + @Override + public MethodDefinition[] methods() { + return new MethodDefinition[]{ + new Methods.LoadFromCursorMethod(this), + new Methods.ExistenceMethod(this), + new Methods.PrimaryConditionMethod(this) + }; + } + + private CreationQueryBehavior creationQueryBehavior; + + public int priority; + + @Override + public Behaviors.AssociationalBehavior associationalBehavior() { + return new Behaviors.AssociationalBehavior( + StringUtils.isNullOrEmpty(modelView.name())? modelClassName() : modelView.name(), + ProcessorUtils.extractTypeNameFromAnnotation(modelView, e -> null, it -> { + it.database(); + return null; + }), + modelView.allFields()); + } + + @Override + public Behaviors.CursorHandlingBehavior cursorHandlingBehavior() { + return new Behaviors.CursorHandlingBehavior(modelView.orderedCursorLookUp(), modelView.assignDefaultValuesFromCursor()); + } + + @Override + public void prepareForWriteInternal() { + queryFieldName = null; + if(typeElement != null) { + createColumnDefinitions(typeElement); + } + } + + @Override + public void createColumnDefinitions(TypeElement typeElement) { + BasicColumnGenerator columnGenerator = new BasicColumnGenerator(manager); + List variableElements = ElementUtility.getAllElements(typeElement, manager); + for (Element element : variableElements) { + classElementLookUpMap.put(element.getSimpleName().toString(), element); + } + + Validators.ColumnValidator columnValidator = new Validators.ColumnValidator(); + for (Element variableElement : variableElements) { + boolean isValidAllFields = ElementUtility.isValidAllFields(associationalBehavior().allFields, variableElement); + boolean isColumnMap = variableElement.getAnnotation(ColumnMap.class) != null; + + if (variableElement.getAnnotation(Column.class) != null || isValidAllFields || isColumnMap) { + // package private, will generate helper + boolean isPackagePrivate = ElementUtility.isPackagePrivate(variableElement); + ColumnDefinition columnDefinition = columnGenerator.generate(variableElement, this); + if(columnDefinition != null) { + if (columnValidator.validate(manager, columnDefinition)) { + columnDefinitions.add(columnDefinition); + if (isPackagePrivate) { + packagePrivateList.add(columnDefinition); + } + } + + if (columnDefinition.type.isPrimaryField()) { + manager.logError("ModelView "+elementName+" cannot have primary keys"); + } + } + } else if (variableElement.getAnnotation(ModelViewQuery.class) != null) { + if (!StringUtils.isNullOrEmpty(queryFieldName)) { + manager.logError("Found duplicate queryField name: "+queryFieldName+" for " + elementClassName); + } + + ExecutableElement element = (ExecutableElement)ElementExtensions.toTypeErasedElement(variableElement, ProcessorManager.manager); + if (element != null) { + TypeElement returnElement = ElementExtensions.toTypeElement(element.getReturnType(), ProcessorManager.manager); + ProcessorUtils.ensureVisibleStatic(element, typeElement, "ModelViewQuery"); + if (!ProcessorUtils.implementsClass(returnElement, manager.processingEnvironment, com.dbflow5.processor.ClassNames.QUERY)) { + manager.logError("The function "+variableElement.getSimpleName()+" must return "+com.dbflow5.processor.ClassNames.QUERY+" from " + elementName); + } + } + + queryFieldName = variableElement.getSimpleName().toString(); + } + } + + if (StringUtils.isNullOrEmpty(queryFieldName)) { + manager.logError(elementClassName + " is missing the @ModelViewQuery field."); + } + } + + @Override + public List primaryColumnDefinitions() { + return columnDefinitions; + } + + @Override + public TypeName extendsClass() { + return ParameterizedTypeName.get(com.dbflow5.processor.ClassNames.MODEL_VIEW_ADAPTER, elementClassName); + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + FieldSpec.Builder fieldBuilder = FieldSpec.builder(String.class, "VIEW_NAME"); + fieldBuilder.initializer(CodeBlock.builder().add("\""+associationalBehavior().name+"\"").build()); + typeBuilder.addField(fieldBuilder.build()); + + if(elementClassName != null) { + columnDefinitions.forEach(columnDefinition -> columnDefinition.addPropertyDefinition(typeBuilder, elementClassName)); + } + + this.writeConstructor(typeBuilder); + + writeGetModelClass(typeBuilder, elementClassName); + + creationQueryBehavior.addToType(typeBuilder); + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getCreationQuery") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(String.class) + .addAnnotation(Override.class) + .addStatement("return " + "\"CREATE VIEW IF NOT EXISTS "+StringUtils.quoteIfNeeded(associationalBehavior().name)+" AS \" + $T.$L().getQuery()", elementClassName, queryFieldName); + typeBuilder.addMethod(methodBuilder.build()); + associationalBehavior().writeName(typeBuilder); + + JavaPoetExtensions.overrideFun(typeBuilder, ClassNames.OBJECT_TYPE, "getType", builder -> { + builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + builder.addStatement("return " + "$T.View", ClassNames.OBJECT_TYPE); + return null; + }); + + MethodDefinition[] methodDefinitionArray = methods(); + for(MethodDefinition definition : methodDefinitionArray) { + if(definition != null && definition.methodSpec() != null) { + typeBuilder.addMethod(definition.methodSpec()); + } + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/OneToManyDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/OneToManyDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..8af62050770050a0c6b4db546f6a0137871e3cf7 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/OneToManyDefinition.java @@ -0,0 +1,208 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.OneToMany; +import com.dbflow5.annotation.OneToManyMethod; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.column.ColumnAccessCombiner; +import com.dbflow5.processor.definition.column.ColumnAccessor; +import com.dbflow5.processor.utils.ElementExtensions; +import com.dbflow5.processor.utils.ModelUtils; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.WildcardTypeName; + +import javax.lang.model.element.*; +import javax.lang.model.type.TypeMirror; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Description: Represents the [OneToMany] annotation. + */ +public class OneToManyDefinition extends BaseDefinition { + + private String _methodName; + + public String variableName; + + public List methods = new ArrayList<>(); + + public boolean isLoad() { + return isAll() || methods.contains(OneToManyMethod.LOAD); + } + + public boolean isAll() { + return methods.contains(OneToManyMethod.ALL); + } + + public boolean isDelete() { + return isAll() || methods.contains(OneToManyMethod.DELETE); + } + + public boolean isSave() { + return isAll() || methods.contains(OneToManyMethod.SAVE); + } + + public TypeName referencedTableType = null; + public boolean hasWrapper = false; + + private ColumnAccessor columnAccessor; + private boolean extendsModel = false; + private TypeElement referencedType = null; + + private boolean efficientCodeMethods = false; + + public OneToManyDefinition(ExecutableElement executableElement, ProcessorManager processorManager, List parentElements){ + super(executableElement, processorManager); + OneToMany oneToMany = executableElement.getAnnotation(OneToMany.class); + + efficientCodeMethods = oneToMany.efficientMethods(); + + _methodName = executableElement.getSimpleName().toString(); + variableName = oneToMany.variableName(); + if (variableName.isEmpty()) { + variableName = _methodName.replaceFirst("get", ""); + variableName = variableName.substring(0, 1).toLowerCase() + variableName.substring(1); + } + + ColumnAccessor.PrivateScopeColumnAccessor privateAccessor = new ColumnAccessor.PrivateScopeColumnAccessor(variableName, new ColumnAccessor.GetterSetter() { + @Override + public String getterName() { + return ""; + } + + @Override + public String setterName() { + return "set" + StringUtils.capitalize(variableName); + } + }, false, hasWrapper? ModelUtils.wrapper : ""); + + boolean isVariablePrivate = false; + Element referencedElement = null; + if(parentElements != null && parentElements.size() > 0) { + Element element = parentElements.get(0); + if(element.getSimpleName() != null && element.getSimpleName().toString().equals(variableName)) { + referencedElement = element; + } + } + + if (referencedElement == null) { + // check on setter. if setter exists, we can reference it safely since a getter has already been defined. + if (!parentElements.stream().anyMatch(it -> it.getSimpleName().toString().equals(privateAccessor.setterNameElement()) )) { + manager.logError(OneToManyDefinition.class, + "@OneToMany definition $elementName " + + "Cannot find setter ${privateAccessor.setterNameElement} " + + "for variable $variableName."); + } else { + isVariablePrivate = true; + } + } else { + isVariablePrivate = referencedElement.getModifiers().contains(Modifier.PRIVATE); + } + + methods.addAll(Arrays.asList(oneToMany.oneToManyMethods())); + + List parameters = executableElement.getParameters(); + if (!parameters.isEmpty()) { + if (parameters.size() > 1) { + manager.logError(OneToManyDefinition.class, "OneToMany Methods can only have one parameter and that be the DatabaseWrapper."); + } else { + VariableElement param = parameters.get(0); + TypeName name = TypeName.get(param.asType()); + if (name == com.dbflow5.processor.ClassNames.DATABASE_WRAPPER) { + hasWrapper = true; + } else { + manager.logError(OneToManyDefinition.class, "OneToMany Methods can only specify a ${com.dbflow5.processor.ClassNames.DATABASE_WRAPPER} as its parameter."); + } + } + } + + columnAccessor = isVariablePrivate? privateAccessor : new ColumnAccessor.VisibleScopeColumnAccessor(variableName); + + TypeMirror returnType = executableElement.getReturnType(); + TypeName typeName = TypeName.get(returnType); + if (typeName instanceof ParameterizedTypeName) { + List typeArguments = ((ParameterizedTypeName) typeName).typeArguments; + if (typeArguments.size() == 1) { + TypeName refTableType = typeArguments.get(0); + if (refTableType instanceof WildcardTypeName) { + refTableType = ((WildcardTypeName) refTableType).upperBounds.get(0); + } + referencedTableType = refTableType; + + referencedType = ElementExtensions.toTypeElement(referencedTableType, manager); + extendsModel = ProcessorUtils.isSubclass(referencedType, manager.processingEnvironment, com.dbflow5.processor.ClassNames.MODEL); + } + } + + methodName = ModelUtils.variable + "."+_methodName+"("+ ColumnAccessCombiner.wrapperIfBaseModel(hasWrapper)+")"; + } + + private final String methodName; + + /** + * Writes the method to the specified builder for loading from DB. + */ + public void writeLoad(CodeBlock.Builder codeBuilder) { + if (isLoad()) { + codeBuilder.addStatement(methodName); + } + } + + /** + * Writes a delete method that will delete all related objects. + */ + public void writeDelete(MethodSpec.Builder method) { + if (isDelete()) { + writeLoopWithMethod(method, "delete"); + method.addStatement(columnAccessor.set(CodeBlock.of("null"), ColumnAccessor.modelBlock, false)); + } + } + + public void writeSave(MethodSpec.Builder codeBuilder) { + if (isSave()) writeLoopWithMethod(codeBuilder, "save"); + } + + public void writeUpdate(MethodSpec.Builder codeBuilder) { + if (isSave()) writeLoopWithMethod(codeBuilder, "update"); + } + + public void writeInsert(MethodSpec.Builder codeBuilder) { + if (isSave()) writeLoopWithMethod(codeBuilder, "insert"); + } + + private void writeLoopWithMethod(MethodSpec.Builder codeBuilder, String methodName) { + String oneToManyMethodName = this.methodName; + String statement = oneToManyMethodName + " != null"; + CodeBlock.Builder builder = CodeBlock.builder().beginControlFlow("if" + (com.dbflow5.StringUtils.isNullOrEmpty(statement)? "" : " ("+statement+")")); + + // need to load adapter for non-model classes + if (!extendsModel || efficientCodeMethods) { + codeBuilder.addStatement("$T adapter = $T.getModelAdapter($T.class)", + ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, referencedTableType), + ClassNames.FLOW_MANAGER, referencedTableType); + } + + if (efficientCodeMethods) { + codeBuilder.addStatement("adapter."+methodName+"All("+oneToManyMethodName+""+ColumnAccessCombiner.wrapperCommaIfBaseModel(true)+")"); + } else { + String forStatement = "$T value: " + oneToManyMethodName; + CodeBlock.builder().beginControlFlow("for" + (com.dbflow5.StringUtils.isNullOrEmpty(forStatement)? "" : " ("+forStatement+")"), ClassName.get(referencedType)); + + if (!extendsModel) { + codeBuilder.addStatement("adapter."+methodName+"(value"+ColumnAccessCombiner.wrapperCommaIfBaseModel(true)+")"); + } else { + codeBuilder.addStatement("value."+methodName+"("+ColumnAccessCombiner.wrapperIfBaseModel(true)+")"); + } + } + builder.endControlFlow(); + } +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/QueryModelDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/QueryModelDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..54a226966d59eec0d6ac616b4e9b75be7cbbaafd --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/QueryModelDefinition.java @@ -0,0 +1,146 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.annotation.Column; +import com.dbflow5.annotation.ColumnMap; +import com.dbflow5.annotation.QueryModel; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.Validators; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.utils.ElementUtility; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import java.util.List; + +/** + * Description: + */ +public class QueryModelDefinition extends EntityDefinition { + private final Behaviors.AssociationalBehavior associationalBehavior; + private final Behaviors.CursorHandlingBehavior cursorHandlingBehavior; + + public QueryModelDefinition(Behaviors.AssociationalBehavior associationalBehavior, Behaviors.CursorHandlingBehavior cursorHandlingBehavior, + TypeElement typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager); + this.associationalBehavior = associationalBehavior; + this.cursorHandlingBehavior = cursorHandlingBehavior; + + setOutputClassName("_QueryTable"); + processorManager.addModelToDatabase(elementClassName, associationalBehavior.databaseTypeName); + } + + @Override + public Behaviors.AssociationalBehavior associationalBehavior() { + return associationalBehavior; + } + + @Override + public Behaviors.CursorHandlingBehavior cursorHandlingBehavior() { + return cursorHandlingBehavior; + } + + @Override + public MethodDefinition[] methods() { + return new MethodDefinition[]{ + new Methods.LoadFromCursorMethod(this), + new Methods.ExistenceMethod(this), + new Methods.PrimaryConditionMethod(this) + }; + } + + public QueryModelDefinition(QueryModel queryModel, TypeElement typeElement, ProcessorManager processorManager) { + this( + new Behaviors.AssociationalBehavior(typeElement.getSimpleName().toString(), ProcessorUtils.extractTypeNameFromAnnotation(queryModel, e -> null, queryModel1 -> { + queryModel.database(); + return null; + }), queryModel.allFields()), + new Behaviors.CursorHandlingBehavior(queryModel.orderedCursorLookUp(), queryModel.assignDefaultValuesFromCursor()), + typeElement, processorManager); + } + + /** + * [ColumnMap] constructor. + */ + public QueryModelDefinition(TypeElement typeElement, TypeName databaseTypeName, ProcessorManager processorManager) { + this(new Behaviors.AssociationalBehavior(typeElement.getSimpleName().toString(),databaseTypeName,true), + new Behaviors.CursorHandlingBehavior(false, true), + typeElement, processorManager); + } + + public void prepareForWriteInternal() { + if(typeElement != null) { + createColumnDefinitions(typeElement); + } + } + + @Override + public TypeName extendsClass() { + return ParameterizedTypeName.get(ClassNames.RETRIEVAL_ADAPTER, elementClassName); + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + if(elementClassName != null) { + columnDefinitions.forEach(it -> { + it.addPropertyDefinition(typeBuilder, elementClassName); + }); + } + + writeGetModelClass(typeBuilder, elementClassName); + this.writeConstructor(typeBuilder); + + MethodDefinition[] methodArray = methods(); + for(MethodDefinition definition : methodArray) { + if(definition != null){ + typeBuilder.addMethod(definition.methodSpec()); + } + } + } + + @Override + public void createColumnDefinitions(TypeElement typeElement) { + BasicColumnGenerator columnGenerator = new BasicColumnGenerator(manager); + List variableElements = ElementUtility.getAllElements(typeElement, manager); + for (Element element : variableElements) { + classElementLookUpMap.put(element.getSimpleName().toString(), element); + } + + Validators.ColumnValidator columnValidator = new Validators.ColumnValidator(); + for (Element variableElement : variableElements) { + + // no private static or final fields + boolean isAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement); + // package private, will generate helper + boolean isColumnMap = variableElement.getAnnotation(ColumnMap.class) != null; + + if (variableElement.getAnnotation(Column.class) != null || isAllFields || isColumnMap) { + boolean isPackagePrivate = ElementUtility.isPackagePrivate(element); + ColumnDefinition columnDefinition = columnGenerator.generate(variableElement, this); + if(columnDefinition != null) { + if (columnValidator.validate(manager, columnDefinition)) { + columnDefinitions.add(columnDefinition); + if (isPackagePrivate) { + packagePrivateList.add(columnDefinition); + } + } + + if (columnDefinition.type.isPrimaryField()) { + manager.logError("QueryModel $elementName cannot have primary keys"); + } + } + } + } + } + + @Override + public List primaryColumnDefinitions() { + return columnDefinitions; + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/TableDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/TableDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..ec6a7df3df207458e334fe9e798f079f4c5c04f2 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/TableDefinition.java @@ -0,0 +1,767 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.*; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.Validators; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.definition.behavior.ColumnBehaviors; +import com.dbflow5.processor.definition.behavior.CreationQueryBehavior; +import com.dbflow5.processor.definition.behavior.FTSBehaviors; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.definition.column.DefinitionUtils; +import com.dbflow5.processor.definition.column.ReferenceColumnDefinition; +import com.dbflow5.processor.utils.ElementUtility; +import com.dbflow5.processor.utils.ModelUtils; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.*; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +/** + * Description: Used in writing ModelAdapters + */ +public class TableDefinition extends EntityDefinition { + private final Table table; + + public enum Type { + Normal, + FTS3, + FTS4; + + boolean isVirtual() { + return this != Normal; + } + } + + public String insertConflictActionName = ""; + + public String updateConflictActionName = ""; + + public String primaryKeyConflictActionName = ""; + + public List _primaryColumnDefinitions = new ArrayList<>(); + public List foreignKeyDefinitions = new ArrayList<>(); + public List columnMapDefinitions = new ArrayList<>(); + public List uniqueGroupsDefinitions = new ArrayList<>(); + public List indexGroupsDefinitions = new ArrayList<>(); + + public boolean implementsContentValuesListener; + + public boolean implementsSqlStatementListener; + + public TableDefinition(Table table, ProcessorManager manager, TypeElement element) { + super(element, manager); + this.table = table; + + creationQueryBehavior = new CreationQueryBehavior(table.createWithDatabase()); + useIsForPrivateBooleans = table.useBooleanGetterSetters(); + generateContentValues = table.generateContentValues(); + + //init + setOutputClassName("_Table"); + + manager.addModelToDatabase(elementClassName, associationalBehavior().databaseTypeName); + + Fts4 fts4 = element.getAnnotation(Fts4.class); + Fts3 fts3 = element.getAnnotation(Fts3.class); + + if (fts3 != null && fts4 != null) { + manager.logError("Table " + elementClassName + " cannot have multiple FTS annotations."); + } + + if (fts4 != null) { + type = Type.FTS4; + } else if (fts3 != null) { + type = Type.FTS3; + } else { + type = Type.Normal; + } + + if (type == Type.FTS4) { + ftsBehavior = new FTSBehaviors.FTS4Behavior(ProcessorUtils.extractTypeNameFromAnnotation(fts4, e -> null, it -> { + it.contentTable(); + return null; + }), associationalBehavior().databaseTypeName, elementName, manager); + } else if (type == Type.FTS3) { + ftsBehavior = new FTSBehaviors.FTS3Behavior(elementName, manager); + } else { + ftsBehavior = null; + } + + com.dbflow5.annotation.InheritedColumn[] inheritedColumns = table.inheritedColumns(); + for (com.dbflow5.annotation.InheritedColumn it : inheritedColumns) { + if (inheritedFieldNameList.contains(it.fieldName())) { + manager.logError("A duplicate inherited column with name " + it.fieldName() + " " + + "was found for " + associationalBehavior().name); + } + inheritedFieldNameList.add(it.fieldName()); + inheritedColumnMap.put(it.fieldName(), it); + } + + InheritedPrimaryKey[] inheritedPrimaryKeys = table.inheritedPrimaryKeys(); + for (InheritedPrimaryKey it : inheritedPrimaryKeys) { + if (inheritedFieldNameList.contains(it.fieldName())) { + manager.logError("A duplicate inherited column with name ${it.fieldName} " + + "was found for " + associationalBehavior().name); + } + inheritedFieldNameList.add(it.fieldName()); + inheritedPrimaryKeyMap.put(it.fieldName(), it); + } + + implementsContentValuesListener = ProcessorUtils.implementsClass(element, manager.processingEnvironment, + ClassNames.CONTENT_VALUES_LISTENER); + + implementsSqlStatementListener = ProcessorUtils.implementsClass(element, manager.processingEnvironment, + ClassNames.SQLITE_STATEMENT_LISTENER); + + contentValueMethods = new MethodDefinition[]{new Methods.BindToContentValuesMethod(this, true, implementsContentValuesListener), + new Methods.BindToContentValuesMethod(this, false, implementsContentValuesListener)}; + + temporary = table.temporary(); + } + + @Override + public MethodDefinition[] methods() { + return new MethodDefinition[]{ + new Methods.BindToStatementMethod(this, Methods.BindToStatementMethod.Mode.INSERT), + new Methods.BindToStatementMethod(this, Methods.BindToStatementMethod.Mode.UPDATE), + new Methods.BindToStatementMethod(this, Methods.BindToStatementMethod.Mode.DELETE), + new Methods.InsertStatementQueryMethod(this, Methods.InsertStatementQueryMethod.Mode.INSERT), + new Methods.InsertStatementQueryMethod(this, Methods.InsertStatementQueryMethod.Mode.SAVE), + new Methods.UpdateStatementQueryMethod(this), + new Methods.DeleteStatementQueryMethod(this), + new Methods.CreationQueryMethod(this), + new Methods.LoadFromCursorMethod(this), + new Methods.ExistenceMethod(this), + new Methods.PrimaryConditionMethod(this), + new Methods.OneToManyDeleteMethod(this, false), + new Methods.OneToManyDeleteMethod(this, true), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_SAVE, false), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_SAVE, true), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_INSERT, false), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_INSERT, true), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_UPDATE, false), + new Methods.OneToManySaveMethod(this, Methods.OneToManySaveMethod.METHOD_UPDATE, true) + }; + } + + private final MethodDefinition[] contentValueMethods; + + private final CreationQueryBehavior creationQueryBehavior; + public boolean useIsForPrivateBooleans; + private final boolean generateContentValues; + + public List oneToManyDefinitions = new ArrayList<>(); + + private final Map columnMap = new LinkedHashMap<>(); + private final Map> columnUniqueMap = new LinkedHashMap<>(); + private final Map inheritedColumnMap = new HashMap<>(); + private final List inheritedFieldNameList = new ArrayList<>(); + private final Map inheritedPrimaryKeyMap = new HashMap<>(); + + public boolean hasPrimaryConstructor = false; + + private Behaviors.AssociationalBehavior associationalBehavior; + private Behaviors.CursorHandlingBehavior cursorHandlingBehavior; + private Behaviors.CachingBehavior cachingBehavior; + + @Override + public Behaviors.AssociationalBehavior associationalBehavior() { + if (associationalBehavior == null) { + associationalBehavior = new Behaviors.AssociationalBehavior( + StringUtils.isNullOrEmpty(table.name()) ? element.getSimpleName().toString() : table.name(), + ProcessorUtils.extractTypeNameFromAnnotation(table, e -> null, it -> { + it.database(); + return null; + }), table.allFields()); + } + return associationalBehavior; + } + + @Override + public Behaviors.CursorHandlingBehavior cursorHandlingBehavior() { + if (cursorHandlingBehavior == null) { + cursorHandlingBehavior = new Behaviors.CursorHandlingBehavior( + table.orderedCursorLookUp(), + table.assignDefaultValuesFromCursor()); + } + return cursorHandlingBehavior; + } + + public Behaviors.CachingBehavior cachingBehavior() { + if (cachingBehavior == null) { + cachingBehavior = new Behaviors.CachingBehavior( + table.cachingEnabled(), + table.cacheSize(), + null, + null); + } + return cachingBehavior; + } + + public Type type; + + public FTSBehaviors.FtsBehavior ftsBehavior; + + public boolean temporary; + + @Override + public void prepareForWriteInternal() { + columnMap.clear(); + _primaryColumnDefinitions.clear(); + uniqueGroupsDefinitions.clear(); + indexGroupsDefinitions.clear(); + foreignKeyDefinitions.clear(); + columnMapDefinitions.clear(); + columnUniqueMap.clear(); + oneToManyDefinitions.clear(); + cachingBehavior.clear(); + + // globular default + ConflictAction insertConflict = table.insertConflict(); + if (insertConflict == ConflictAction.NONE && databaseDefinition().insertConflict != ConflictAction.NONE) { + insertConflict = databaseDefinition().insertConflict; + } + + ConflictAction updateConflict = table.updateConflict(); + if (updateConflict == ConflictAction.NONE && databaseDefinition().updateConflict != ConflictAction.NONE) { + updateConflict = databaseDefinition().updateConflict; + } + + ConflictAction primaryKeyConflict = table.primaryKeyConflict(); + + insertConflictActionName = insertConflict == ConflictAction.NONE ? "" : insertConflict.name(); + updateConflictActionName = updateConflict == ConflictAction.NONE ? "" : updateConflict.name(); + primaryKeyConflictActionName = primaryKeyConflict == ConflictAction.NONE ? "" : primaryKeyConflict.name(); + + if (typeElement != null) { + createColumnDefinitions(typeElement); + } + + UniqueGroup[] groups = table.uniqueColumnGroups(); + Set uniqueNumbersSet = new HashSet<>(); + for (UniqueGroup uniqueGroup : groups) { + if (uniqueNumbersSet.contains(uniqueGroup.groupNumber())) { + manager.logError("A duplicate unique group with number" + + " " + uniqueGroup.groupNumber() + " was found for " + associationalBehavior.name); + } + UniqueGroupsDefinition definition = new UniqueGroupsDefinition(uniqueGroup); + for (ColumnDefinition it : columnDefinitions) { + if (it.uniqueGroups.contains(definition.number)) { + definition.addColumnDefinition(it); + } + } + + uniqueGroupsDefinitions.add(definition); + uniqueNumbersSet.add(uniqueGroup.groupNumber()); + } + + IndexGroup[] indexGroups = table.indexGroups(); + uniqueNumbersSet = new HashSet<>(); + for (IndexGroup indexGroup : indexGroups) { + if (uniqueNumbersSet.contains(indexGroup.number())) { + manager.logError(TableDefinition.class, "A duplicate unique index number" + + " " + indexGroup.number() + " was found for " + elementName); + } + IndexGroupsDefinition definition = new IndexGroupsDefinition(this, indexGroup); + for (ColumnDefinition it : columnDefinitions) { + if (it.indexGroups.contains(definition.indexNumber)) { + definition.columnDefinitionList.add(it); + } + } + + indexGroupsDefinitions.add(definition); + uniqueNumbersSet.add(indexGroup.number()); + } + } + + @Override + public void createColumnDefinitions(TypeElement typeElement) { + List elements = ElementUtility.getAllElements(typeElement, manager); + + for (Element element : elements) { + classElementLookUpMap.put(element.getSimpleName().toString(), element); + if (element instanceof ExecutableElement && ((ExecutableElement) element).getParameters().isEmpty() + && element.getSimpleName().toString().equals("") + && element.getEnclosingElement() == typeElement + && !element.getModifiers().contains(Modifier.PRIVATE)) { + hasPrimaryConstructor = true; + } + } + + if (!hasPrimaryConstructor) { + manager.logError("For now, tables must have a visible, default, parameterless constructor. In" + + " Kotlin all field parameters must have default values."); + } + + Validators.ColumnValidator columnValidator = new Validators.ColumnValidator(); + Validators.OneToManyValidator oneToManyValidator = new Validators.OneToManyValidator(); + elements.forEach(variableElement -> { + // no private static or final fields for all columns, or any inherited columns here. + boolean isAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement); + + // package private, will generate helper + boolean isPackagePrivate = ElementUtility.isPackagePrivate(variableElement); + boolean isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, variableElement, this.element); + + boolean isForeign = variableElement.getAnnotation(ForeignKey.class) != null; + boolean isPrimary = variableElement.getAnnotation(PrimaryKey.class) != null; + boolean isInherited = inheritedColumnMap.containsKey(variableElement.getSimpleName().toString()); + boolean isInheritedPrimaryKey = inheritedPrimaryKeyMap.containsKey(variableElement.getSimpleName().toString()); + boolean isColumnMap = variableElement.getAnnotation(ColumnMap.class) != null; + if (variableElement.getAnnotation(Column.class) != null || isForeign || isPrimary + || isAllFields || isInherited || isInheritedPrimaryKey || isColumnMap) { + + if (checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage, variableElement)) return; + + ColumnDefinition columnDefinition; + if (isInheritedPrimaryKey) { + InheritedPrimaryKey inherited = inheritedPrimaryKeyMap.get(variableElement.getSimpleName().toString()); + columnDefinition = new ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage, + inherited != null ? inherited.column() : null, inherited != null ? inherited.primaryKey() : null, ConflictAction.NONE); + } else if (isInherited) { + InheritedColumn inherited = inheritedColumnMap.get(variableElement.getSimpleName().toString()); + columnDefinition = new ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage, + inherited != null ? inherited.column() : null, null, inherited != null ? inherited.nonNullConflict() : ConflictAction.NONE); + } else if (isForeign) { + columnDefinition = new ReferenceColumnDefinition(variableElement.getAnnotation(ForeignKey.class), manager, this, + variableElement, isPackagePrivateNotInSamePackage); + } else if (isColumnMap) { + columnDefinition = new ReferenceColumnDefinition(variableElement.getAnnotation(ColumnMap.class), + manager, this, variableElement, isPackagePrivateNotInSamePackage); + } else { + columnDefinition = new ColumnDefinition(manager, variableElement, + this, isPackagePrivateNotInSamePackage, element.getAnnotation(Column.class), element.getAnnotation(PrimaryKey.class), ConflictAction.NONE); + } + + if (columnValidator.validate(manager, columnDefinition)) { + columnDefinitions.add(columnDefinition); + if (isPackagePrivate) { + packagePrivateList.add(columnDefinition); + } + columnMap.put(columnDefinition.columnName, columnDefinition); + // check to ensure not null. + if (columnDefinition.type instanceof ColumnDefinition.Type.Primary) { + _primaryColumnDefinitions.add(columnDefinition); + } else if (columnDefinition.type instanceof ColumnDefinition.Type.PrimaryAutoIncrement) { + this.primaryKeyColumnBehavior = new ColumnBehaviors.PrimaryKeyColumnBehavior(false, columnDefinition, true); + } else if (columnDefinition.type instanceof ColumnDefinition.Type.RowId) { + this.primaryKeyColumnBehavior = new ColumnBehaviors.PrimaryKeyColumnBehavior(true, columnDefinition, false); + } + + if (primaryKeyColumnBehavior.associatedColumn != null) { + // check to ensure not null. + if (primaryKeyColumnBehavior.associatedColumn.isNullableType) { + manager.logWarning("Attempting to use nullable field type on an autoincrementing column. " + + "To suppress or remove this warning " + + "switch to java primitive, add @android.support.annotation.NonNull," + + "@org.jetbrains.annotation.NotNull, or in Kotlin don't make it nullable. Check the column " + primaryKeyColumnBehavior.associatedColumn.columnName + " " + + "on ${associationalBehavior.name}"); + } + } + + if (ftsBehavior != null) { + ftsBehavior.validateColumnDefinition(columnDefinition); + } + + if (columnDefinition instanceof ReferenceColumnDefinition) { + if (!((ReferenceColumnDefinition) columnDefinition).isColumnMap()) { + foreignKeyDefinitions.add((ReferenceColumnDefinition) columnDefinition); + } else { + columnMapDefinitions.add((ReferenceColumnDefinition) columnDefinition); + } + } + + if (!columnDefinition.uniqueGroups.isEmpty()) { + for (Integer group : columnDefinition.uniqueGroups) { + columnUniqueMap.getOrDefault(group, new LinkedHashSet<>()) + .add(columnDefinition); + } + } + } + } else if (variableElement.getAnnotation(OneToMany.class) != null) { + OneToManyDefinition oneToManyDefinition = new OneToManyDefinition((ExecutableElement) variableElement, manager, elements); + if (oneToManyValidator.validate(manager, oneToManyDefinition)) { + oneToManyDefinitions.add(oneToManyDefinition); + } + } else { + cachingBehavior.evaluateElement(variableElement, typeElement, manager); + } + }); + + // ignore any referenced one to many field definitions from all fields. + columnDefinitions = columnDefinitions.stream().filter(column -> oneToManyDefinitions.isEmpty() || oneToManyDefinitions.stream().noneMatch(it -> it.variableName.equals(column.elementName))).collect(Collectors.toList()); + } + + @Override + public List primaryColumnDefinitions() { + if (primaryKeyColumnBehavior().associatedColumn != null) { + return Collections.singletonList(primaryKeyColumnBehavior().associatedColumn); + } else { + if (ftsBehavior != null) { + return columnDefinitions; + } else { + return _primaryColumnDefinitions; + } + } + } + + @Override + public TypeName extendsClass() { + return ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, elementClassName); + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + // check references to properly set them up. + foreignKeyDefinitions.forEach(ReferenceColumnDefinition::checkNeedsReferences); + columnMapDefinitions.forEach(ReferenceColumnDefinition::checkNeedsReferences); + + writeGetModelClass(typeBuilder, elementClassName); + this.writeConstructor(typeBuilder); + associationalBehavior.writeName(typeBuilder); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("getType") + .returns(ClassNames.OBJECT_TYPE) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return $T.Table", ClassNames.OBJECT_TYPE) + .build()); + + if (StringUtils.isNotNullOrEmpty(updateConflictActionName)) { + typeBuilder.addMethod(MethodSpec + .methodBuilder("getUpdateOnConflictAction") + .returns(ClassNames.CONFLICT_ACTION) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return $T." + updateConflictActionName, ClassNames.CONFLICT_ACTION) + .build()); + + } + + if (StringUtils.isNotNullOrEmpty(insertConflictActionName)) { + typeBuilder.addMethod(MethodSpec + .methodBuilder("getInsertOnConflictAction") + .returns(ClassNames.CONFLICT_ACTION) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return $T." + insertConflictActionName, ClassNames.CONFLICT_ACTION) + .build()); + } + + String paramColumnName = "columnName"; + CodeBlock.Builder getPropertiesBuilder = CodeBlock.builder(); + + MethodSpec.Builder methodBuilder = MethodSpec + .methodBuilder("getProperty") + .returns(ClassNames.PROPERTY) + .addParameter(ParameterSpec.builder(String.class, paramColumnName).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("String " + paramColumnName + "2 = $T.quoteIfNeeded(" + paramColumnName + ")", ClassNames.STRING_UTILS); + + methodBuilder.beginControlFlow("switch (" + "(" + paramColumnName + "2)" + ")"); + + for (int i = 0; i < columnDefinitions.size(); i++) { + if (i > 0) { + getPropertiesBuilder.add(","); + } + ColumnDefinition columnDefinition = columnDefinitions.get(i); + if (elementClassName != null) { + columnDefinition.addPropertyDefinition(typeBuilder, elementClassName); + } + columnDefinition.addPropertyCase(methodBuilder); + columnDefinition.addColumnName(getPropertiesBuilder); + } + + methodBuilder.beginControlFlow("default:").addStatement("throw new $T(\"Invalid column name passed. Ensure you are calling the correct table's column\")", IllegalArgumentException.class).endControlFlow(); + methodBuilder.endControlFlow(); + + typeBuilder.addMethod(methodBuilder.build()); + + + typeBuilder.addField(FieldSpec.builder(ArrayTypeName.of(ClassNames.IPROPERTY), "ALL_COLUMN_PROPERTIES") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer(CodeBlock.builder().add("new $T[]{$L}", ClassNames.IPROPERTY, getPropertiesBuilder.build().toString()).build()) + .build()); + + // add index properties here + for (IndexGroupsDefinition indexGroupsDefinition : indexGroupsDefinitions) { + typeBuilder.addField(indexGroupsDefinition.fieldSpec()); + } + + if (primaryKeyColumnBehavior.hasAutoIncrement || primaryKeyColumnBehavior.hasRowID) { + ColumnDefinition autoIncrement = primaryKeyColumnBehavior.associatedColumn; + if (autoIncrement != null) { + typeBuilder.addMethod(MethodSpec + .methodBuilder("updateAutoIncrement") + .returns(TypeName.VOID) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addParameter(ParameterSpec.builder(Number.class, "id").build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(autoIncrement.updateAutoIncrementMethod()) + .build()); + } + } + + List saveForeignKeyFields = new ArrayList<>(); + columnDefinitions.forEach(it -> { + if ((it instanceof ReferenceColumnDefinition) + && ((ReferenceColumnDefinition) it).foreignKeyColumnBehavior != null + && ((ReferenceColumnDefinition) it).foreignKeyColumnBehavior.saveForeignKeyModel) { + saveForeignKeyFields.add((ReferenceColumnDefinition) it); + } + }); + + if (!saveForeignKeyFields.isEmpty()) { + CodeBlock.Builder code = CodeBlock.builder(); + saveForeignKeyFields.forEach(it -> it.appendSaveMethod(code)); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("saveForeignKeys") + .returns(TypeName.VOID) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(code.build()) + .build()); + } + + List deleteForeignKeyFields = new ArrayList<>(); + columnDefinitions.forEach(it -> { + if ((it instanceof ReferenceColumnDefinition) + && ((ReferenceColumnDefinition) it).foreignKeyColumnBehavior != null + && ((ReferenceColumnDefinition) it).foreignKeyColumnBehavior.deleteForeignKeyModel) { + deleteForeignKeyFields.add((ReferenceColumnDefinition) it); + } + }); + + if (!deleteForeignKeyFields.isEmpty()) { + CodeBlock.Builder code = CodeBlock.builder(); + deleteForeignKeyFields.forEach(it -> it.appendDeleteMethod(code)); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("deleteForeignKeys") + .returns(TypeName.VOID) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(code.build()) + .build()); + } + + typeBuilder.addMethod(MethodSpec + .methodBuilder("getAllColumnProperties") + .returns(ArrayTypeName.of(ClassNames.IPROPERTY)) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return ALL_COLUMN_PROPERTIES") + .build()); + + creationQueryBehavior.addToType(typeBuilder); + + if (cachingBehavior.cachingEnabled) { + int customCacheSize = cachingBehavior.customCacheSize; + String customCacheFieldName = cachingBehavior.customCacheFieldName; + String customMultiCacheFieldName = cachingBehavior.customMultiCacheFieldName; + + FieldSpec.Builder fieldBuilder = FieldSpec + .builder(ClassNames.CACHE_ADAPTER, "cacheAdapter") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + List primaryColumns = primaryColumnDefinitions(); + + boolean hasCustomField = StringUtils.isNotNullOrEmpty(customCacheFieldName); + boolean hasCustomMultiCacheField = StringUtils.isNotNullOrEmpty(customMultiCacheFieldName); + List typeClasses = new ArrayList<>(); + String typeArgumentsString; + if (hasCustomField) { + typeClasses.add(elementClassName); + typeArgumentsString = "$T." + customCacheFieldName; + } else { + typeClasses.add(ClassNames.SIMPLE_MAP_CACHE); + typeArgumentsString = "new $T(" + customCacheSize + ")"; + } + typeArgumentsString += ", ${" + primaryColumns.size() + "}"; + if (hasCustomMultiCacheField) { + typeClasses.add(elementClassName); + typeArgumentsString += ", $T." + customMultiCacheFieldName; + } else { + typeArgumentsString += ", null"; + } + + TypeSpec.Builder typeSpec = TypeSpec.anonymousClassBuilder(typeArgumentsString, typeClasses) + .addSuperinterface(ParameterizedTypeName.get(ClassNames.CACHE_ADAPTER, elementTypeName)); + + if (primaryColumns.size() > 1) { + MethodSpec.Builder methodSpecBuilder = MethodSpec + .methodBuilder("getCachingColumnValuesFromModel") + .returns(ArrayTypeName.of(Object.class)) + .addParameter(ParameterSpec.builder(ArrayTypeName.of(Object.class), "inValues").build()) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + for (int i = 0; i < primaryColumns.size(); i++) { + ColumnDefinition column = primaryColumns.get(i); + methodSpecBuilder.addCode(column.getColumnAccessString(i)); + } + + methodBuilder.addStatement("return inValues"); + + typeSpec.addMethod(methodSpecBuilder.build()); + + + MethodSpec.Builder methodSpecBuilder2 = MethodSpec + .methodBuilder("getCachingColumnValuesFromCursor") + .returns(ArrayTypeName.of(Object.class)) + .addParameter(ParameterSpec.builder(ArrayTypeName.of(Object.class), "inValues").build()) + .addParameter(ParameterSpec.builder(ClassNames.FLOW_CURSOR, "cursor").build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + for (int i = 0; i < primaryColumns.size(); i++) { + ColumnDefinition column = primaryColumns.get(i); + String method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.complexColumnBehavior.wrapperTypeName); + methodSpecBuilder2.addStatement("inValues[" + i + "] = " + Methods.LoadFromCursorMethod.PARAM_CURSOR + + "." + method + "(" + Methods.LoadFromCursorMethod.PARAM_CURSOR + ".getColumnIndex(\"" + column.columnName + "\"))"); + } + methodSpecBuilder2.addStatement("return inValues"); + + typeSpec.addMethod(methodSpecBuilder2.build()); + } else { + // single primary key + typeBuilder.addMethod(MethodSpec + .methodBuilder("getCachingColumnValueFromModel") + .returns(Object.class) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(primaryColumns.get(0).getSimpleAccessString()) + .build()); + + ColumnDefinition column = primaryColumns.get(0); + String method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.complexColumnBehavior.wrapperTypeName); + typeBuilder.addMethod(MethodSpec + .methodBuilder("getCachingColumnValueFromCursor") + .returns(Object.class) + .addParameter(ParameterSpec.builder(ClassNames.FLOW_CURSOR, "cursor").build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return " + Methods.LoadFromCursorMethod.PARAM_CURSOR + "." + method + "(" + Methods.LoadFromCursorMethod.PARAM_CURSOR + ".getColumnIndex(\"" + column.columnName + "\"))") + .build()); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("getCachingId") + .returns(Object.class) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return " + "getCachingColumnValueFromModel(" + ModelUtils.variable + ")") + .build()); + } + + if (!foreignKeyDefinitions.isEmpty()) { + CodeBlock.Builder codeBuilder2 = CodeBlock.builder(); + + AtomicInteger noIndex = new AtomicInteger(-1); + NameAllocator nameAllocator = new NameAllocator(); + foreignKeyDefinitions.forEach(it -> codeBuilder2.add(it.getLoadFromCursorMethod(false, noIndex, nameAllocator))); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("reloadRelationships") + .returns(TypeName.VOID) + .addParameter(ParameterSpec.builder(elementClassName, ModelUtils.variable).build()) + .addParameter(ParameterSpec.builder(ClassNames.FLOW_CURSOR, Methods.LoadFromCursorMethod.PARAM_CURSOR).build()) + .addParameter(ParameterSpec.builder(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(codeBuilder2.build()) + .build()); + } + + codeBuilder.add("$L", typeSpec.build()); + + fieldBuilder.initializer(codeBuilder.build()); + typeBuilder.addField(fieldBuilder.build()); + + + boolean singlePrimaryKey = primaryColumnDefinitions().size() == 1; + + typeBuilder.addMethod(MethodSpec + .methodBuilder("createSingleModelLoader") + .returns(ClassNames.SINGLE_MODEL_LOADER) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return new $T<>(getTable(), cacheAdapter)", singlePrimaryKey ? ClassNames.SINGLE_KEY_CACHEABLE_MODEL_LOADER : ClassNames.CACHEABLE_MODEL_LOADER) + .build()); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("createListModelLoader") + .returns(ClassNames.LIST_MODEL_LOADER) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return " + "new $T<>(getTable(), cacheAdapter)", singlePrimaryKey ? ClassNames.SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER : ClassNames.CACHEABLE_LIST_MODEL_LOADER) + .build()); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("createListModelSaver") + .returns(ParameterizedTypeName.get(ClassNames.CACHEABLE_LIST_MODEL_SAVER, elementClassName)) + .addAnnotation(Override.class) + .addModifiers(Modifier.PROTECTED) + .addStatement("return " + "new $T<>(getModelSaver(), cacheAdapter)", ClassNames.CACHEABLE_LIST_MODEL_SAVER) + .build()); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("cachingEnabled") + .returns(TypeName.BOOLEAN) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return " + true) + .build()); + + typeBuilder.addMethod(MethodSpec + .methodBuilder("load") + .returns(elementClassName) + .addParameter(ParameterSpec.builder(elementClassName, "model").build()) + .addParameter(ParameterSpec.builder(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper).build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("$T loaded = super.load(model, " + ModelUtils.wrapper + ")", elementClassName) + .addStatement("cacheAdapter.storeModelInCache(model)") + .addStatement("return loaded") + .build()); + + } + + MethodDefinition[] methods = methods(); + for (MethodDefinition definition : methods) { + if (definition != null) { + typeBuilder.addMethod(definition.methodSpec()); + } + } + + if (generateContentValues) { + for (MethodDefinition definition : contentValueMethods) { + if (definition != null) { + typeBuilder.addMethod(definition.methodSpec()); + } + } + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/TypeConverterDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/TypeConverterDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..f68cbec843cc38a80750867610e7421fc51bab89 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/TypeConverterDefinition.java @@ -0,0 +1,67 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.annotation.TypeConverter; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import java.util.ArrayList; +import java.util.List; + +/** + * Description: Holds data about type converters in order to write them. + */ +public class TypeConverterDefinition { + + public ClassName className; + public boolean isDefaultConverter; + + public TypeName modelTypeName; + public TypeName dbTypeName; + public List allowedSubTypes; + + public TypeConverterDefinition(TypeConverter typeConverter, ClassName className, TypeMirror typeMirror, ProcessorManager manager, boolean isDefaultConverter) { + this.className = className; + this.isDefaultConverter = isDefaultConverter; + + List allowedSubTypes = new ArrayList<>(); + if (typeConverter != null) { + try { + typeConverter.allowedSubtypes(); + } catch (MirroredTypesException e) { + List types = e.getTypeMirrors(); + types.forEach(it -> { + allowedSubTypes.add(TypeName.get(it)); + }); + } + } + this.allowedSubTypes = allowedSubTypes; + + Types types = manager.typeUtils; + + DeclaredType typeConverterSuper = null; + DeclaredType typeConverterType = manager.typeUtils.getDeclaredType(manager.elements + .getTypeElement(ClassNames.TYPE_CONVERTER.toString())); + + for (TypeMirror superType : types.directSupertypes(typeMirror)) { + TypeMirror erasure = types.erasure(superType); + if (types.isAssignable(erasure, typeConverterType) || erasure.toString() == typeConverterType.toString()) { + typeConverterSuper = (DeclaredType) superType; + } + } + + if (typeConverterSuper != null) { + List typeArgs = typeConverterSuper.getTypeArguments(); + dbTypeName = ClassName.get(typeArgs.get(0)); + modelTypeName = ClassName.get(typeArgs.get(1)); + } else { + dbTypeName = null; + modelTypeName = null; + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/TypeDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/TypeDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..554178a826a5f98f17e75d4540054f8666ba1316 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/TypeDefinition.java @@ -0,0 +1,11 @@ +package com.dbflow5.processor.definition; + +import com.squareup.javapoet.TypeSpec; + +/** + * Description: Simple interface for returning a [TypeSpec]. + */ +public interface TypeDefinition { + TypeSpec typeSpec(); +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/UniqueGroupsDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/UniqueGroupsDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..fe4b25d49bf785672b53288b34781edd3eb926a7 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/UniqueGroupsDefinition.java @@ -0,0 +1,44 @@ +package com.dbflow5.processor.definition; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.annotation.UniqueGroup; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.definition.column.ReferenceColumnDefinition; +import com.squareup.javapoet.CodeBlock; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: + */ +public class UniqueGroupsDefinition { + + public List columnDefinitionList = new ArrayList<>(); + public int number; + + private final ConflictAction uniqueConflict; + + public UniqueGroupsDefinition(UniqueGroup uniqueGroup) { + number = uniqueGroup.groupNumber(); + uniqueConflict = uniqueGroup.uniqueConflict(); + } + + public void addColumnDefinition(ColumnDefinition columnDefinition) { + columnDefinitionList.add(columnDefinition); + } + + public CodeBlock creationName() { + CodeBlock.Builder codeBuilder = CodeBlock.builder().add(", UNIQUE("); + codeBuilder.add(StringUtils.joinToString(columnDefinitionList, ", ", columnDefinition -> { + if (columnDefinition instanceof ReferenceColumnDefinition) { + return StringUtils.joinToString(((ReferenceColumnDefinition) columnDefinition).referenceDefinitionList(), ", ", it -> StringUtils.quote(it.columnName())); + } else { + return StringUtils.quote(columnDefinition.columnName); + } + })); + codeBuilder.add(") ON CONFLICT $L", uniqueConflict); + return codeBuilder.build(); + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/behavior/Behaviors.java b/processor/src/main/java/com/dbflow5/processor/definition/behavior/Behaviors.java new file mode 100644 index 0000000000000000000000000000000000000000..91a324eae44fb69e586cbd08e12eaa8be31c7b3b --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/behavior/Behaviors.java @@ -0,0 +1,109 @@ +package com.dbflow5.processor.definition.behavior; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.ModelCacheField; +import com.dbflow5.annotation.MultiCacheField; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +public class Behaviors{ + /** + * Defines how a class is named, db it belongs to, and other loading behaviors. + */ + public static class AssociationalBehavior { + /** + * The name of this object in the database. Default is the class name. + */ + public String name; + /** + * The class of the database this corresponds to. + */ + public TypeName databaseTypeName; + /** + * When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . + * The only required annotated field becomes The [PrimaryKey] + * or [PrimaryKey.autoincrement]. + */ + public boolean allFields; + + public AssociationalBehavior(String name, TypeName databaseTypeName, boolean allFields) { + this.name = name; + this.databaseTypeName = databaseTypeName; + this.allFields = allFields; + } + + public void writeName(TypeSpec.Builder typeSpec) { + typeSpec.addMethod(MethodSpec + .methodBuilder("getName") + .returns(TypeName.get(String.class)) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return " + StringUtils.quote(name)) + .build()); + } + } + + + /** + * Defines how a Cursor gets loaded from the DB. + */ + public static class CursorHandlingBehavior { + public boolean orderedCursorLookup; + public boolean assignDefaultValuesFromCursor = true; + + public CursorHandlingBehavior(boolean orderedCursorLookup, boolean assignDefaultValuesFromCursor) { + this.orderedCursorLookup = orderedCursorLookup; + this.assignDefaultValuesFromCursor = assignDefaultValuesFromCursor; + } + } + + /** + * Describes caching behavior of a [TableDefinition]. + */ + public static class CachingBehavior { + public boolean cachingEnabled; + public int customCacheSize; + public String customCacheFieldName; + public String customMultiCacheFieldName; + + public CachingBehavior(boolean cachingEnabled, int customCacheSize, String customCacheFieldName, String customMultiCacheFieldName) { + this.cachingEnabled = cachingEnabled; + this.customCacheSize = customCacheSize; + this.customCacheFieldName = customCacheFieldName; + this.customMultiCacheFieldName = customMultiCacheFieldName; + } + + public void clear() { + customCacheFieldName = null; + customMultiCacheFieldName = null; + } + + /** + * If applicable, we store the [customCacheFieldName] or [customMultiCacheFieldName] for reference. + */ + public void evaluateElement(Element element, TypeElement typeElement, ProcessorManager manager) { + if (element.getAnnotation(ModelCacheField.class) != null) { + ProcessorUtils.ensureVisibleStatic(element, typeElement, "ModelCacheField"); + if (!StringUtils.isNullOrEmpty(customCacheFieldName)) { + manager.logError("ModelCacheField can only be declared once from: $typeElement"); + } else { + customCacheFieldName = element.getSimpleName().toString(); + } + } else if (element.getAnnotation(MultiCacheField.class) != null) { + ProcessorUtils.ensureVisibleStatic(element, typeElement, "MultiCacheField"); + if (!StringUtils.isNullOrEmpty(customMultiCacheFieldName)) { + manager.logError("MultiCacheField can only be declared once from: $typeElement"); + } else { + customMultiCacheFieldName = element.getSimpleName().toString(); + } + } + } + } +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/behavior/ColumnBehaviors.java b/processor/src/main/java/com/dbflow5/processor/definition/behavior/ColumnBehaviors.java new file mode 100644 index 0000000000000000000000000000000000000000..2b344b53263503bf723f14dca6d08baa0d1239dc --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/behavior/ColumnBehaviors.java @@ -0,0 +1,44 @@ +package com.dbflow5.processor.definition.behavior; + +import com.dbflow5.annotation.ForeignKeyAction; +import com.dbflow5.processor.definition.column.ColumnDefinition; + +public class ColumnBehaviors { + /** + * Defines how Primary Key columns behave. If has autoincrementing column or ROWID, the [associatedColumn] is not null. + */ + public static class PrimaryKeyColumnBehavior{ + public boolean hasRowID; + /** + * Either [hasRowID] or [hasAutoIncrement] or null. + */ + public ColumnDefinition associatedColumn; + public boolean hasAutoIncrement; + + public PrimaryKeyColumnBehavior(boolean hasRowID, ColumnDefinition associatedColumn, boolean hasAutoIncrement) { + this.hasRowID = hasRowID; + this.associatedColumn = associatedColumn; + this.hasAutoIncrement = hasAutoIncrement; + } + } + + + /** + * Defines how Foreign Key columns behave. + */ + public static class ForeignKeyColumnBehavior{ + public ForeignKeyAction onDelete; + public ForeignKeyAction onUpdate; + public boolean saveForeignKeyModel; + public boolean deleteForeignKeyModel; + public boolean deferred; + + public ForeignKeyColumnBehavior(ForeignKeyAction onDelete, ForeignKeyAction onUpdate, boolean saveForeignKeyModel, boolean deleteForeignKeyModel, boolean deferred) { + this.onDelete = onDelete; + this.onUpdate = onUpdate; + this.saveForeignKeyModel = saveForeignKeyModel; + this.deleteForeignKeyModel = deleteForeignKeyModel; + this.deferred = deferred; + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.java b/processor/src/main/java/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..63ff2c959bd7edba239961cc168207b3e2fe3443 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.java @@ -0,0 +1,138 @@ +package com.dbflow5.processor.definition.behavior; + +import com.dbflow5.data.Blob; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.TypeConverterDefinition; +import com.dbflow5.processor.definition.column.ColumnAccessor; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +/** + * Description: Consolidates a column's wrapping behavior. + */ +public class ComplexColumnBehavior { + + private final TypeName columnClassName; + + /** + * The parent column if within a [ReferenceDefinition], or the column itself if a [ColumnDefinition]. + */ + private final ColumnDefinition columnDefinition; + + /** + * The column that it is referencing for type information if its a [ReferenceDefinition]. + * It's itself if its a [ColumnDefinition]. + */ + private final ColumnDefinition referencedColumn; + + private final boolean referencedColumnHasCustomConverter; + public ClassName typeConverterClassName; + public TypeMirror typeMirror; + private final ProcessorManager manager; + + + public boolean hasCustomConverter = false; + public boolean hasTypeConverter = false; + + public ColumnAccessor wrapperAccessor = null; + public TypeName wrapperTypeName = null; + + // Wraps for special cases such as for a Blob converter since we cannot use conventional converter + public ColumnAccessor subWrapperAccessor = null; + + public ComplexColumnBehavior(TypeName columnClassName, ColumnDefinition columnDefinition, + ColumnDefinition referencedColumn, boolean referencedColumnHasCustomConverter, + ClassName typeConverterClassName, TypeMirror typeMirror, ProcessorManager manager) { + this.columnClassName = columnClassName; + this.columnDefinition = columnDefinition; + this.referencedColumn = referencedColumn; + this.referencedColumnHasCustomConverter = referencedColumnHasCustomConverter; + this.typeConverterClassName = typeConverterClassName; + this.typeMirror = typeMirror; + this.manager = manager; + + handleSpecifiedTypeConverter(typeConverterClassName, typeMirror); + evaluateIfWrappingNecessary(referencedColumn.element, manager); + } + + private void handleSpecifiedTypeConverter(ClassName typeConverterClassName, TypeMirror typeMirror) { + if (typeConverterClassName != null && typeMirror != null && + typeConverterClassName != com.dbflow5.processor.ClassNames.TYPE_CONVERTER) { + evaluateTypeConverter(new TypeConverterDefinition(null, typeConverterClassName, typeMirror, manager, false), true); + } + } + + private void evaluateIfWrappingNecessary(Element element, ProcessorManager processorManager) { + TypeName elementTypeName = referencedColumn.elementTypeName; + if (!hasCustomConverter) { + TypeElement typeElement = ProcessorUtils.getTypeElement(element); + if (typeElement != null && typeElement.getKind() == ElementKind.ENUM) { + wrapperAccessor = new ColumnAccessor.EnumColumnAccessor(elementTypeName, null); + wrapperTypeName = ClassName.get(String.class); + } else if (elementTypeName == ClassName.get(Blob.class)) { + wrapperAccessor = new ColumnAccessor.BlobColumnAccessor(null); + wrapperTypeName = ArrayTypeName.of(TypeName.BYTE); + } else { + if (elementTypeName instanceof ParameterizedTypeName || + elementTypeName.equals(ArrayTypeName.of(TypeName.BYTE.unbox()))) { + // do nothing, for now. + } else if (elementTypeName instanceof ArrayTypeName) { + processorManager.messager.printMessage(Diagnostic.Kind.ERROR, + "Columns cannot be of array type. Found "+elementTypeName); + } else { + if(elementTypeName == TypeName.BOOLEAN) { + wrapperAccessor = new ColumnAccessor.BooleanColumnAccessor(null); + wrapperTypeName = TypeName.BOOLEAN; + }else if(elementTypeName == TypeName.CHAR) { + wrapperAccessor = new ColumnAccessor.CharColumnAccessor(null); + wrapperTypeName = TypeName.CHAR; + }else if(elementTypeName == TypeName.BYTE) { + wrapperAccessor = new ColumnAccessor.ByteColumnAccessor(null); + wrapperTypeName = TypeName.BYTE; + }else { + TypeConverterDefinition definition = processorManager.getTypeConverterDefinition(elementTypeName); + evaluateTypeConverter(definition, referencedColumnHasCustomConverter); + } + } + } + } + } + + private void evaluateTypeConverter(TypeConverterDefinition typeConverter, boolean isCustom) { + // Any annotated members, otherwise we will use the scanner to find other ones + if (typeConverter.modelTypeName != columnClassName) { + manager.logError("The specified custom TypeConverter's Model Value " + + "${typeConverter.modelTypeName} from ${typeConverter.className}" + + " must match the type of the column $columnClassName."); + } else { + hasTypeConverter = true; + hasCustomConverter = isCustom; + + String fieldName; + if (hasCustomConverter) { + fieldName = columnDefinition.entityDefinition + .addColumnForCustomTypeConverter(columnDefinition, typeConverter.className); + } else { + fieldName = columnDefinition.entityDefinition + .addColumnForTypeConverter(columnDefinition, typeConverter.className); + } + + wrapperAccessor = new ColumnAccessor.TypeConverterScopeColumnAccessor(fieldName, null); + wrapperTypeName = typeConverter.dbTypeName; + + // special case of blob + if (wrapperTypeName == ClassName.get(Blob.class)) { + subWrapperAccessor = new ColumnAccessor.BlobColumnAccessor(null); + } + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.java b/processor/src/main/java/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..bf96e69da0fabdca6e54e7a287bcfbb084dab122 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.java @@ -0,0 +1,33 @@ +package com.dbflow5.processor.definition.behavior; + +import com.dbflow5.processor.definition.Adders; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Modifier; + +/** + * Description: + */ +public class CreationQueryBehavior implements Adders.TypeAdder { + + public final boolean createWithDatabase; + + public CreationQueryBehavior(boolean createWithDatabase){ + this.createWithDatabase = createWithDatabase; + } + + @Override + public void addToType(TypeSpec.Builder typeBuilder) { + if (!createWithDatabase) { + typeBuilder.addMethod(MethodSpec + .methodBuilder("createWithDatabase") + .returns(TypeName.BOOLEAN) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return false") + .build()); + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/behavior/FTSBehaviors.java b/processor/src/main/java/com/dbflow5/processor/definition/behavior/FTSBehaviors.java new file mode 100644 index 0000000000000000000000000000000000000000..2cdf2dcbf6dbca06f3b44f38d2dbfb2d02fe3283 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/behavior/FTSBehaviors.java @@ -0,0 +1,89 @@ +package com.dbflow5.processor.definition.behavior; + +import com.dbflow5.StringUtils; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.TableDefinition; +import com.dbflow5.processor.definition.column.ColumnDefinition; +import com.dbflow5.processor.utils.ElementExtensions; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; + +public class FTSBehaviors { + public interface FtsBehavior { + ProcessorManager manager(); + String elementName(); + + default void validateColumnDefinition(ColumnDefinition columnDefinition) { + if (columnDefinition.type != ColumnDefinition.Type.RowId.INSTANCE + && !columnDefinition.columnName.equals("rowid") + && ElementExtensions.isOneOf(columnDefinition.elementTypeName, Integer.class, Long.class)) { + manager().logError("FTS4 Table of type $elementName can only have a single primary key named \"rowid\" of type rowid that is an Int or Long type."); + } else if (columnDefinition.elementTypeName != ClassName.get(String.class)) { + manager().logError("FTS4 Table of type $elementName must only contain String columns"); + } + } + + default void addContentTableCode(boolean addComma, CodeBlock.Builder codeBlock) { + + } + } + + /** + * Description: + */ + public static class FTS4Behavior implements FtsBehavior { + public TypeName contentTable; + private final TypeName databaseTypeName; + + private final String elementName; + private final ProcessorManager manager; + + @Override + public ProcessorManager manager() { + return manager; + } + + @Override + public String elementName() { + return elementName; + } + + public FTS4Behavior(TypeName contentTable, TypeName databaseTypeName, String elementName, ProcessorManager manager) { + this.contentTable = contentTable; + this.databaseTypeName = databaseTypeName; + this.elementName = elementName; + this.manager = manager; + } + + public void addContentTableCode(boolean addComma, CodeBlock.Builder codeBlock) { + TableDefinition tableDefinition = manager.getTableDefinition(databaseTypeName, contentTable); + if(tableDefinition != null) { + if (addComma) { + codeBlock.add(", "); + } + codeBlock.add("content="+ StringUtils.quote(tableDefinition.associationalBehavior().name)); + } + } + } + + public static class FTS3Behavior implements FtsBehavior{ + private final String elementName; + private final ProcessorManager manager; + + @Override + public ProcessorManager manager() { + return manager; + } + + @Override + public String elementName() { + return elementName; + } + + public FTS3Behavior(String elementName, ProcessorManager manager) { + this.elementName = elementName; + this.manager = manager; + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessCombiner.java b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessCombiner.java new file mode 100644 index 0000000000000000000000000000000000000000..bc7cb6c9a1d855a9833e6ff568f12f864823adda --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessCombiner.java @@ -0,0 +1,422 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.StringUtils; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.SQLiteHelper; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.utils.ModelUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.NameAllocator; +import com.squareup.javapoet.TypeName; + +public abstract class ColumnAccessCombiner { + + private final NameAllocator nameAllocator = new NameAllocator(); + + public Combiner combiner; + + public ColumnAccessCombiner(Combiner combiner) { + this.combiner = combiner; + } + + public CodeBlock getFieldAccessBlock(CodeBlock.Builder existingBuilder, + CodeBlock modelBlock, + boolean useWrapper, + boolean defineProperty) { + CodeBlock fieldAccess; + if (combiner.wrapperLevelAccessor != null && !combiner.fieldTypeName.isPrimitive()) { + fieldAccess = CodeBlock.of(nameAllocator.newName(combiner.customPrefixName) + "ref" + combiner.fieldLevelAccessor.propertyName); + + if (defineProperty) { + CodeBlock fieldAccessorBlock = combiner.fieldLevelAccessor.get(modelBlock); + CodeBlock wrapperAccessorBlock = combiner.wrapperLevelAccessor.get(fieldAccessorBlock); + // if same, don't extra null check. + if (!combiner.fieldLevelAccessor.toString().equals(combiner.wrapperLevelAccessor.toString()) + && !(combiner.wrapperLevelAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor)) { + existingBuilder.addStatement("$T $L = $L != null ? $L : null", + combiner.wrapperFieldTypeName, fieldAccess, fieldAccessorBlock, wrapperAccessorBlock); + } else if (combiner.wrapperLevelAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor) { + existingBuilder.addStatement("$T $L = $L", combiner.wrapperFieldTypeName, + fieldAccess, wrapperAccessorBlock); + } else { + existingBuilder.addStatement("$T $L = $L", combiner.wrapperFieldTypeName, + fieldAccess, fieldAccessorBlock); + } + } + } else { + if (useWrapper && combiner.wrapperLevelAccessor != null) { + fieldAccess = combiner.wrapperLevelAccessor.get(combiner.fieldLevelAccessor.get(modelBlock)); + } else { + fieldAccess = combiner.fieldLevelAccessor.get(modelBlock); + } + } + return fieldAccess; + } + + public abstract void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, + CodeBlock modelBlock, + boolean defineProperty); + + public void addNull(CodeBlock.Builder code, String columnRepresentation, int index) { + + } + + public static class Combiner { + public ColumnAccessor fieldLevelAccessor; + public TypeName fieldTypeName; + public ColumnAccessor wrapperLevelAccessor; + public TypeName wrapperFieldTypeName; + public ColumnAccessor subWrapperAccessor; + public String customPrefixName; + + public Combiner(ColumnAccessor fieldLevelAccessor, TypeName fieldTypeName, ColumnAccessor wrapperLevelAccessor, + TypeName wrapperFieldTypeName, ColumnAccessor subWrapperAccessor, String customPrefixName) { + this.fieldLevelAccessor = fieldLevelAccessor; + this.fieldTypeName = fieldTypeName; + this.wrapperLevelAccessor = wrapperLevelAccessor; + this.wrapperFieldTypeName = wrapperFieldTypeName; + this.subWrapperAccessor = subWrapperAccessor; + this.customPrefixName = customPrefixName; + } + } + + + public static class SimpleAccessCombiner extends ColumnAccessCombiner { + public SimpleAccessCombiner(Combiner combiner) { + super(combiner); + } + + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + builder.addStatement("return $L", getFieldAccessBlock(builder, modelBlock,true,true)); + } + + } + + public static class ExistenceAccessCombiner extends ColumnAccessCombiner { + public boolean autoRowId; + public boolean quickCheckPrimaryKey; + public ClassName tableClassName; + + public ExistenceAccessCombiner(Combiner combiner, + boolean autoRowId, + boolean quickCheckPrimaryKey, + ClassName tableClassName) { + super(combiner); + this.autoRowId = autoRowId; + this.quickCheckPrimaryKey = quickCheckPrimaryKey; + this.tableClassName = tableClassName; + } + + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + + if (autoRowId) { + CodeBlock access = getFieldAccessBlock(builder, modelBlock, true, true); + + builder.add("return "); + + if (!combiner.fieldTypeName.isPrimitive()) { + builder.add("($L != null && ", access); + } + builder.add("$L > 0", access); + + if (!combiner.fieldTypeName.isPrimitive()) { + builder.add(" || $L == null)", access); + } + } + + if (!autoRowId || !quickCheckPrimaryKey) { + if (autoRowId) { + builder.add("\n&& "); + } else { + builder.add("return "); + } + + builder.add("$T.selectCountOf()\n.from($T.class)\n" + + ".where(getPrimaryConditionClause($L))\n" + + ".hasData(wrapper)", + ClassNames.SQLITE, tableClassName, modelBlock); + } + builder.add(";\n"); + } + + } + + public static class ContentValuesCombiner extends ColumnAccessCombiner { + + public ContentValuesCombiner(Combiner combiner) { + super(combiner); + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + CodeBlock fieldAccess = getFieldAccessBlock(builder, modelBlock,true,true); + if (combiner.fieldTypeName.isPrimitive()) { + builder.addStatement("values.put($1S, $2L)", StringUtils.quote(columnRepresentation), fieldAccess); + } else { + if (defaultValue != null) { + CodeBlock subWrapperFieldAccess = fieldAccess; + if (combiner.subWrapperAccessor != null) { + subWrapperFieldAccess = combiner.subWrapperAccessor.get(fieldAccess); + } + if (!fieldAccess.toString().equals(subWrapperFieldAccess.toString()) + || !defaultValue.toString().equals("null")) { + builder.addStatement("values.put($S, $L != null ? $L : $L)", + StringUtils.quote(columnRepresentation), fieldAccess, subWrapperFieldAccess, defaultValue); + } else { + // if same default value is null and object reference is same as subwrapper. + builder.addStatement("values.put($S, $L)", + StringUtils.quote(columnRepresentation), fieldAccess); + } + } else { + builder.addStatement("values.put($S, $L)", + StringUtils.quote(columnRepresentation), fieldAccess); + } + } + } + + @Override + public void addNull(CodeBlock.Builder code, String columnRepresentation, int index) { + code.addStatement("values.putNull($S)", StringUtils.quote(columnRepresentation)); + } + } + + public static class SqliteStatementAccessCombiner extends ColumnAccessCombiner { + public SqliteStatementAccessCombiner(Combiner combiner) { + super(combiner); + } + + public void addCode(CodeBlock.Builder builder, String columnRepresentation, + CodeBlock defaultValue, int index, + CodeBlock modelBlock, boolean defineProperty) { + CodeBlock fieldAccess = getFieldAccessBlock(builder, modelBlock,true, defineProperty); + String wrapperMethod = SQLiteHelper.getWrapperMethod(combiner.wrapperFieldTypeName != null? combiner.wrapperFieldTypeName : combiner.fieldTypeName); + String statementMethod = SQLiteHelper.get(combiner.wrapperFieldTypeName != null? combiner.wrapperFieldTypeName : combiner.fieldTypeName).sqLiteStatementMethod; + + String offset = index + " + " + columnRepresentation; + if (StringUtils.isNullOrEmpty(columnRepresentation)) { + offset = String.valueOf(index); + } + if (combiner.fieldTypeName.isPrimitive()) { + builder.addStatement("statement.bind"+statementMethod+"("+offset+", "+fieldAccess+")"); + } else { + if(combiner.subWrapperAccessor != null) { + CodeBlock subWrapperFieldAccess = combiner.subWrapperAccessor.get(fieldAccess) != null? combiner.subWrapperAccessor.get(fieldAccess) : fieldAccess; + if (!StringUtils.isNullOrEmpty(defaultValue.toString())) { + if(fieldAccess != null) { + builder.addStatement("statement.bind"+wrapperMethod+"("+offset+", "+subWrapperFieldAccess+")"); + }else { + builder.addStatement("statement.bind"+statementMethod+"("+offset+", "+defaultValue+")"); + } + } else { + if (combiner.subWrapperAccessor != null) { + builder.addStatement("statement.bind"+wrapperMethod+"OrNull("+offset+", "+fieldAccess+" != null ? "+subWrapperFieldAccess+" : null)"); + } else { + builder.addStatement("statement.bind"+wrapperMethod+"OrNull("+offset+", "+subWrapperFieldAccess+")"); + } + + } + } + } + } + + @Override + public void addNull(CodeBlock.Builder code, String columnRepresentation, int index) { + String access = index + " + " + columnRepresentation; + if (columnRepresentation.isEmpty()) { + access = String.valueOf(index); + } + code.addStatement("statement.bindNull("+access+")"); + } + } + + public static class LoadFromCursorAccessCombiner extends ColumnAccessCombiner { + + public boolean hasDefaultValue; + public NameAllocator nameAllocator; + public Behaviors.CursorHandlingBehavior cursorHandlingBehavior; + + public LoadFromCursorAccessCombiner(Combiner combiner, boolean hasDefaultValue, NameAllocator nameAllocator, Behaviors.CursorHandlingBehavior cursorHandlingBehavior) { + super(combiner); + this.hasDefaultValue = hasDefaultValue; + this.nameAllocator = nameAllocator; + this.cursorHandlingBehavior = cursorHandlingBehavior; + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + CodeBlock indexName; + if (!cursorHandlingBehavior.orderedCursorLookup) { + indexName = CodeBlock.of("\"" + columnRepresentation + "\""); + } else { + indexName = CodeBlock.of(String.valueOf(index)); + } + + if (combiner.wrapperLevelAccessor != null) { + if (!cursorHandlingBehavior.orderedCursorLookup) { + indexName = CodeBlock.of(nameAllocator.newName("index_" + columnRepresentation, columnRepresentation)); + builder.addStatement("$T $L = cursor.getColumnIndex($S)", Integer.class, indexName, columnRepresentation); + builder.beginControlFlow("if ($1L != -1 && !cursor.isNull($1L))", indexName); + } else { + builder.beginControlFlow("if (!cursor.isNull($1L))", index); + } + CodeBlock cursorAccess = CodeBlock.of("cursor.$L($L)", + SQLiteHelper.getMethod(combiner.wrapperFieldTypeName !=null? combiner.wrapperFieldTypeName : combiner.fieldTypeName), indexName); + // special case where we need to append try catch hack + boolean isEnum = combiner.wrapperLevelAccessor instanceof ColumnAccessor.EnumColumnAccessor; + if (isEnum) { + builder.beginControlFlow("try"); + } + if (combiner.subWrapperAccessor != null) { + builder.addStatement(combiner.fieldLevelAccessor.set( + combiner.wrapperLevelAccessor.set(combiner.subWrapperAccessor.set(cursorAccess, null, false), null, false), modelBlock, false)); + } else { + builder.addStatement(combiner.fieldLevelAccessor.set( + combiner.wrapperLevelAccessor.set(cursorAccess, null, false), modelBlock, false)); + } + if (isEnum) { + builder.nextControlFlow("catch ($T e)", IllegalArgumentException.class); + if (cursorHandlingBehavior.assignDefaultValuesFromCursor) { + builder.addStatement(combiner.fieldLevelAccessor.set(combiner.wrapperLevelAccessor.set(defaultValue, null,true), modelBlock, false)); + } else { + builder.addStatement(combiner.fieldLevelAccessor.set(defaultValue, modelBlock, false)); + } + builder.endControlFlow(); + } + if (cursorHandlingBehavior.assignDefaultValuesFromCursor) { + builder.nextControlFlow("else"); + builder.addStatement(combiner.fieldLevelAccessor.set(combiner.wrapperLevelAccessor.set(defaultValue, null,true), modelBlock, false)); + } + builder.endControlFlow(); + } else { + boolean hasDefault = hasDefaultValue; + CodeBlock defaultValueBlock = defaultValue; + if (!cursorHandlingBehavior.assignDefaultValuesFromCursor) { + defaultValueBlock = combiner.fieldLevelAccessor.get(modelBlock); + } else if (!hasDefault && combiner.fieldTypeName.isBoxedPrimitive()) { + hasDefault = true; // force a null on it. + } + CodeBlock cursorAccess = CodeBlock.of("cursor.$LOrDefault($L"+(hasDefault? ", " + defaultValueBlock : "")+")", + SQLiteHelper.getMethod(combiner.wrapperFieldTypeName != null? combiner.wrapperFieldTypeName : combiner.fieldTypeName), indexName); + builder.addStatement(combiner.fieldLevelAccessor.set(cursorAccess, modelBlock, false)); + } + } + } + + public static class PrimaryReferenceAccessCombiner extends ColumnAccessCombiner { + public PrimaryReferenceAccessCombiner(Combiner combiner) { + super(combiner); + } + + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, int index, + CodeBlock modelBlock, boolean defineProperty) { + ColumnAccessor wrapperLevelAccessor = combiner.wrapperLevelAccessor; + builder.addStatement("clause.and($L.$Leq($L))", columnRepresentation, + !wrapperLevelAccessor.isPrimitiveTarget()? "invertProperty()." : "", + getFieldAccessBlock(builder, modelBlock, !(wrapperLevelAccessor instanceof ColumnAccessor.BooleanColumnAccessor), true)); + } + + @Override + public void addNull(CodeBlock.Builder code, String columnRepresentation, int index) { + code.addStatement("clause.and($L.eq(($T) $L))", columnRepresentation, + ClassNames.ICONDITIONAL, "null"); + } + } + + public static class UpdateAutoIncrementAccessCombiner extends ColumnAccessCombiner { + public UpdateAutoIncrementAccessCombiner(Combiner combiner) { + super(combiner); + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + String method = ""; + if (SQLiteHelper.containsNumberMethod(combiner.fieldTypeName.unbox())) { + method = combiner.fieldTypeName.unbox().toString(); + } + + builder.addStatement(combiner.fieldLevelAccessor.set(CodeBlock.of("id.$LValue()", method), modelBlock, false)); + } + + } + + public static class CachingIdAccessCombiner extends ColumnAccessCombiner { + public CachingIdAccessCombiner(Combiner combiner) { + super(combiner); + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, + CodeBlock defaultValue, int index, CodeBlock modelBlock, boolean defineProperty) { + builder.addStatement("inValues[$L] = $L", index, getFieldAccessBlock(builder, modelBlock, true, true)); + } + + } + + public static class SaveModelAccessCombiner extends ColumnAccessCombiner { + public boolean implementsModel; + public boolean extendsBaseModel; + + public SaveModelAccessCombiner(Combiner combiner, boolean implementsModel, boolean extendsBaseModel) { + super(combiner); + this.implementsModel = implementsModel; + this.extendsBaseModel = extendsBaseModel; + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + CodeBlock access = getFieldAccessBlock(builder, modelBlock, true, true); + if(access != null) { + if (implementsModel) { + builder.addStatement(access+".save("+wrapperIfBaseModel(extendsBaseModel)+")").endControlFlow(); + } else { + builder.addStatement("$T.getModelAdapter($T.class).save("+access+", "+ModelUtils.wrapper+")", + ClassNames.FLOW_MANAGER, combiner.fieldTypeName).endControlFlow(); + } + } + } + + } + + public static class DeleteModelAccessCombiner extends ColumnAccessCombiner { + boolean implementsModel; + boolean extendsBaseModel; + + public DeleteModelAccessCombiner(Combiner combiner, boolean implementsModel, boolean extendsBaseModel) { + super(combiner); + this.implementsModel = implementsModel; + this.extendsBaseModel = extendsBaseModel; + } + + @Override + public void addCode(CodeBlock.Builder builder, String columnRepresentation, CodeBlock defaultValue, + int index, CodeBlock modelBlock, boolean defineProperty) { + CodeBlock access = getFieldAccessBlock(builder, modelBlock,true,true); + if(access != null) { + if (implementsModel) { + builder.addStatement(access + ".delete("+wrapperIfBaseModel(extendsBaseModel)+")").endControlFlow(); + } else { + builder.addStatement("$T.getModelAdapter($T.class).delete("+access+", "+ModelUtils.wrapper+")", + ClassNames.FLOW_MANAGER, combiner.fieldTypeName).endControlFlow(); + } + } + } + + } + + public static String wrapperIfBaseModel(boolean extendsBaseModel) { + return extendsBaseModel? ModelUtils.wrapper : ""; + } + + public static String wrapperCommaIfBaseModel(boolean extendsBaseModel) { + return extendsBaseModel? ", " + ModelUtils.wrapper : ""; + } +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessor.java b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessor.java new file mode 100644 index 0000000000000000000000000000000000000000..f71c05af125dd61c1d9632acf64b5ab666626583 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnAccessor.java @@ -0,0 +1,368 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.StringUtils; +import com.dbflow5.data.Blob; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * Description: Base interface for accessing columns + * + * @author Andrew Grosner (fuzz) + */ +public abstract class ColumnAccessor { + + public static CodeBlock modelBlock = CodeBlock.of("model"); + + public String propertyName; + + public boolean isPrimitiveTarget = false; + + public ColumnAccessor(String propertyName) { + this.propertyName = propertyName; + } + + public abstract CodeBlock get(CodeBlock existingBlock); + + public abstract CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault); + + protected void prependPropertyName(CodeBlock.Builder code) { + if(propertyName != null){ + code.add("$L.", propertyName); + } + } + + protected void appendPropertyName(CodeBlock.Builder code) { + if(propertyName != null){ + code.add(".$L", propertyName); + } + } + + protected CodeBlock appendAccess(FunctioncodeAccess) { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + prependPropertyName(codeBuilder); + codeAccess.apply(codeBuilder); + return codeBuilder.build(); + } + + public boolean isPrimitiveTarget() { + return !this.isPrimitiveTarget; + } + + public interface GetterSetter { + String getterName(); + String setterName(); + } + + public static class VisibleScopeColumnAccessor extends ColumnAccessor { + public VisibleScopeColumnAccessor(String propertyName) { + super(propertyName); + } + + @Override + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + CodeBlock.Builder codeBlock = CodeBlock.builder(); + if(baseVariableName != null) { + codeBlock.add("$L.", baseVariableName); + } + return codeBlock.add("$L = $L", propertyName, existingBlock).build(); + } + + public CodeBlock get(CodeBlock existingBlock) { + CodeBlock.Builder codeBlock = CodeBlock.builder(); + if(existingBlock != null){ + codeBlock.add("$L.", existingBlock); + } + return codeBlock.add(propertyName).build(); + } + } + + public static class PrivateScopeColumnAccessor extends ColumnAccessor { + + private String getterName = ""; + private String setterName = ""; + + private final boolean useIsForPrivateBooleans; + private final String optionalGetterParam; + + public PrivateScopeColumnAccessor(String propertyName, GetterSetter getterSetter, boolean useIsForPrivateBooleans, String optionalGetterParam){ + super(propertyName); + this.useIsForPrivateBooleans = useIsForPrivateBooleans; + this.optionalGetterParam = optionalGetterParam; + + if(getterSetter != null){ + getterName = getterSetter.getterName(); + setterName = getterSetter.setterName(); + } + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + CodeBlock.Builder builder = CodeBlock.builder(); + if(existingBlock != null) { + builder.add(existingBlock + "."); + } + builder.add(getterNameElement() + "(" + optionalGetterParam + ")"); + return builder.build(); + } + + @Override + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + CodeBlock.Builder builder = CodeBlock.builder(); + if(baseVariableName != null){ + builder.add(baseVariableName + "."); + } + builder.add(setterNameElement() + "("+existingBlock+")"); + return builder.build(); + } + + public String getterNameElement() { + if (StringUtils.isNullOrEmpty(getterName)) { + if (propertyName != null) { + if (useIsForPrivateBooleans && !propertyName.toLowerCase().startsWith("is")) { + return "is" + StringUtils.capitalize(propertyName); + } else if (!useIsForPrivateBooleans) { + return "get" + StringUtils.capitalize(propertyName); + } else return propertyName.toLowerCase(); + } else { + return ""; + } + } + return getterName; + } + + public String setterNameElement() { + if (propertyName != null) { + String setElementName = propertyName; + if (StringUtils.isNullOrEmpty(setterName)) { + if (!setElementName.toLowerCase().startsWith("set")) { + if (useIsForPrivateBooleans && setElementName.startsWith("is")) { + setElementName = setElementName.replaceFirst("is", ""); + } else if (useIsForPrivateBooleans && setElementName.startsWith("Is")) { + setElementName = setElementName.replaceFirst("Is", ""); + } + return "set" + StringUtils.capitalize(setElementName); + } else return "set" + StringUtils.capitalize(setElementName); + } else return setterName; + } else return ""; + } + } + + public static class PackagePrivateScopeColumnAccessor extends ColumnAccessor { + + public ClassName helperClassName; + public ClassName internalHelperClassName; + + public PackagePrivateScopeColumnAccessor(String propertyName, String packageName, String tableClassName) { + super(propertyName); + helperClassName = ClassName.get(packageName, tableClassName + "_" + classSuffix); + internalHelperClassName = ClassName.get(packageName, tableClassName + "_" + classSuffix); + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + return CodeBlock.of("$T.get$L($L)", internalHelperClassName, + StringUtils.capitalize(propertyName), + existingBlock); + } + + @Override + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return CodeBlock.of("$T.set$L($L, $L)", helperClassName, + StringUtils.capitalize(propertyName), + baseVariableName, + existingBlock); + } + + public static final String classSuffix = "Helper"; + + private static final Map> methodWrittenMap = new HashMap<>(); + + public static boolean containsColumn(ClassName className, String columnName) { + if(methodWrittenMap.get(className) != null) { + return methodWrittenMap.get(className).contains(columnName); + } + return false; + } + + /** + * Ensures we only map and use a package private field generated access method if its necessary. + */ + public static void putElement(ClassName className, String elementName) { + List list = methodWrittenMap.getOrDefault(className, new ArrayList<>()); + if (!list.contains(elementName)) { + list.add(elementName); + } + } + } + + public static class TypeConverterScopeColumnAccessor extends ColumnAccessor { + public String typeConverterFieldName; + + public TypeConverterScopeColumnAccessor(String typeConverterFieldName, String propertyName) { + super(propertyName); + this.typeConverterFieldName = typeConverterFieldName; + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + CodeBlock.Builder codeBlock = CodeBlock.builder(); + codeBlock.add("$L.getDBValue($L", typeConverterFieldName, existingBlock); + appendPropertyName(codeBlock); + codeBlock.add(")"); + return codeBlock.build(); + } + + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + CodeBlock.Builder codeBlock = CodeBlock.builder(); + codeBlock.add("$L.getModelValue($L", typeConverterFieldName, existingBlock); + appendPropertyName(codeBlock); + codeBlock.add(")"); + return codeBlock.build(); + } + } + + public static class EnumColumnAccessor extends ColumnAccessor { + TypeName propertyTypeName; + + public EnumColumnAccessor(TypeName propertyTypeName, String propertyName) { + super(propertyName); + this.propertyTypeName = propertyTypeName; + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + return appendAccess(builder -> { + builder.add("$L.name()", existingBlock); + return null; + }); + } + + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return appendAccess(builder -> { + if (isDefault) { + builder.add(existingBlock); + } else { + builder.add("$T.valueOf($L)", propertyTypeName, existingBlock); + } + return null; + }); + } + } + + public static class BlobColumnAccessor extends ColumnAccessor { + + public BlobColumnAccessor(String propertyName) { + super(propertyName); + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + return appendAccess(builder -> { + builder.add("$L.getBlob()", existingBlock); + return null; + }); + } + + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return appendAccess(builder -> { + if (isDefault) { + builder.add(existingBlock); + } else { + builder.add("new $T($L)", ClassName.get(Blob.class), existingBlock); + } + return null; + }); + } + } + + public static class BooleanColumnAccessor extends ColumnAccessor { + + public BooleanColumnAccessor(String propertyName) { + super(propertyName); + isPrimitiveTarget = true; + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + return appendAccess(builder -> { + builder.add("$L ? 1 : 0", existingBlock); + return null; + }); + } + + @Override + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return appendAccess(builder -> { + if (isDefault) { + builder.add(existingBlock); + } else { + builder.add("$L", existingBlock); + } + return null; + }); + } + } + + public static class CharColumnAccessor extends ColumnAccessor { + + public CharColumnAccessor(String propertyName) { + super(propertyName); + isPrimitiveTarget = true; + } + + @Override + public CodeBlock get(CodeBlock existingBlock) { + return appendAccess(builder -> { + builder.add("new $T(new char[]{$L})", TypeName.get(String.class), existingBlock); + return null; + }); + } + + @Override + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return appendAccess(builder -> { + if (isDefault) { + builder.add(existingBlock); + } else { + builder.add("$L.charAt(0)", existingBlock); + } + return null; + }); + } + + } + + public static class ByteColumnAccessor extends ColumnAccessor { + public ByteColumnAccessor(String propertyName) { + super(propertyName); + isPrimitiveTarget = true; + } + + public CodeBlock get(CodeBlock existingBlock) { + return appendAccess(builder -> { + builder.add("$L", existingBlock); + return null; + }); + } + + public CodeBlock set(CodeBlock existingBlock, CodeBlock baseVariableName, boolean isDefault) { + return appendAccess(builder -> { + if (isDefault) { + builder.add(existingBlock); + } else { + builder.add("($T) $L", TypeName.BYTE, existingBlock); + } + return null; + }); + } + } +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..79497668b4aa55b47d63c64c770a6812df2cb5ee --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/ColumnDefinition.java @@ -0,0 +1,536 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.*; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.BaseDefinition; +import com.dbflow5.processor.definition.EntityDefinition; +import com.dbflow5.processor.definition.TableDefinition; +import com.dbflow5.processor.definition.behavior.Behaviors; +import com.dbflow5.processor.definition.behavior.ComplexColumnBehavior; +import com.dbflow5.processor.utils.ElementExtensions; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.NameAllocator; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +public class ColumnDefinition extends BaseDefinition { + public EntityDefinition entityDefinition; + public Column column; + + public ColumnDefinition(ProcessorManager processorManager, + Element element, + EntityDefinition entityDefinition, + boolean isPackagePrivate, + Column column, + PrimaryKey primaryKey, + ConflictAction notNullConflict) { + super(element, processorManager, null); + init(processorManager, element, isPackagePrivate, primaryKey, notNullConflict); + this.entityDefinition = entityDefinition; + this.column = column; + } + + public static class Type { + private Type() { + } + + public static final class Normal extends ColumnDefinition.Type{ + public static final ColumnDefinition.Type.Normal INSTANCE; + + static { + INSTANCE = new Normal(); + } + + private Normal() { + super(); + } + } + + public static final class Primary extends ColumnDefinition.Type{ + public static final ColumnDefinition.Type.Primary INSTANCE; + + static { + INSTANCE = new Primary(); + } + + private Primary() { + super(); + } + } + + public static final class RowId extends ColumnDefinition.Type{ + public static final ColumnDefinition.Type.RowId INSTANCE; + + static { + INSTANCE = new RowId(); + } + + private RowId() { + super(); + } + } + + public static final class PrimaryAutoIncrement extends ColumnDefinition.Type{ + public boolean quickCheck; + + private PrimaryAutoIncrement(boolean quickCheck) { + super(); + this.quickCheck = quickCheck; + } + } + + public final boolean isPrimaryField() { + return this instanceof Primary + || this instanceof PrimaryAutoIncrement + || this instanceof ColumnDefinition.Type.RowId; + } + } + + private final Pattern QUOTE_PATTERN = Pattern.compile("\".*\""); + + public String columnName = ""; + public String propertyFieldName = ""; + + public Type type = Type.Normal.INSTANCE; + + //var isQuickCheckPrimaryKeyAutoIncrement: Boolean = false + public int length = -1; + public boolean notNull = false; + public boolean isNotNullType = false; + public boolean isNullableType = true; + public ConflictAction onNullConflict = null; + public ConflictAction onUniqueConflict = null; + public boolean unique = false; + + public List uniqueGroups = new ArrayList<>(); + public List indexGroups = new ArrayList<>(); + + public Collate collate = Collate.NONE; + public String defaultValue = null; + + public ColumnAccessor columnAccessor; + + public ComplexColumnBehavior complexColumnBehavior; + + public ColumnAccessCombiner.Combiner combiner; + + public CodeBlock updateStatementBlock() { + return CodeBlock.of(StringUtils.quote(columnName) + "=?"); + } + + public CodeBlock insertStatementColumnName() { + return CodeBlock.of("$L", StringUtils.quote(columnName)); + } + + public CodeBlock insertStatementValuesString() { + if (type instanceof Type.PrimaryAutoIncrement && isNotNullType) { + return CodeBlock.of("nullif(?, 0)"); + } else { + return CodeBlock.of("?"); + } + } + + public List typeConverterElementNames() { + return Collections.singletonList(elementTypeName); + } + + public String primaryKeyName() { + return StringUtils.quote(columnName); + } + + private void init(ProcessorManager processorManager, + Element element, + boolean isPackagePrivate, + PrimaryKey primaryKey, + ConflictAction notNullConflict) { + NotNull notNullAnno = element.getAnnotation(NotNull.class); + if(notNullAnno != null) { + notNull = true; + onNullConflict = notNullAnno.onNullConflict(); + } + + if (onNullConflict == ConflictAction.NONE && notNullConflict != ConflictAction.NONE) { + onNullConflict = notNullConflict; + notNull = true; + } + + if (elementTypeName != null && elementTypeName.isPrimitive()) { + isNullableType = false; + isNotNullType = true; + } + + // if specified, usually from Kotlin targets, we will not set null on the field. + org.jetbrains.annotations.NotNull notNull = element.getAnnotation(org.jetbrains.annotations.NotNull.class); + if(notNull != null) { + isNotNullType = true; + isNullableType = false; + } + + // ohos support annotation + List list = element.getAnnotationMirrors(); + for(AnnotationMirror it : list) { + ClassName className = ElementExtensions.toClassName(ElementExtensions.toTypeElement(it.getAnnotationType(), ProcessorManager.manager), ProcessorManager.manager); + if(className == ClassNames.NON_NULL || className == ClassNames.NON_NULL_X) { + isNotNullType = true; + isNullableType = false; + break; + } + } + + if(column != null){ + if("".equals(column.name())) { + this.columnName = element.getSimpleName().toString(); + }else { + this.columnName = column.name(); + } + length = column.length; + collate = column.collate(); + defaultValue = column.defaultValue(); + + if (StringUtils.isNullOrEmpty(column.defaultValue())) { + defaultValue = null; + } + } + + if (column == null) { + this.columnName = element.getSimpleName().toString(); + } + + boolean isString = (elementTypeName == ClassName.get(String.class)); + if (defaultValue != null + && isString + && !QUOTE_PATTERN.matcher(defaultValue).find()) { + defaultValue = "\"$defaultValue\""; + } + + if (isNotNullType && defaultValue == null + && isString) { + defaultValue = "\"\""; + } + + NameAllocator nameAllocator = new NameAllocator(); + propertyFieldName = nameAllocator.newName(this.columnName); + + if (isPackagePrivate) { + columnAccessor = new ColumnAccessor.PackagePrivateScopeColumnAccessor(elementName, packageName, + ClassName.get((TypeElement)element.getEnclosingElement()).simpleName()); + + ColumnAccessor.PackagePrivateScopeColumnAccessor.putElement( + ((ColumnAccessor.PackagePrivateScopeColumnAccessor)columnAccessor).helperClassName, columnName); + + } else { + boolean isPrivate = element.getModifiers().contains(Modifier.PRIVATE); + if (isPrivate) { + boolean isBoolean = elementTypeName != null && elementTypeName.box() == TypeName.BOOLEAN.box(); + boolean useIs = isBoolean + && entityDefinition instanceof TableDefinition && ((TableDefinition) entityDefinition).useIsForPrivateBooleans; + columnAccessor = new ColumnAccessor.PrivateScopeColumnAccessor(elementName, new ColumnAccessor.GetterSetter() { + @Override + public String getterName() { + return column != null ?column.getterName() : ""; + } + + @Override + public String setterName() { + return column != null ?column.setterName() : ""; + } + }, useIs, ""); + } else { + columnAccessor = new ColumnAccessor.VisibleScopeColumnAccessor(elementName); + } + } + + if (primaryKey != null) { + if(primaryKey.rowID()) { + type = Type.RowId.INSTANCE; + }else if(primaryKey.autoincrement()) { + type = new Type.PrimaryAutoIncrement(primaryKey.quickCheckAutoIncrement()); + }else { + type = Type.Primary.INSTANCE; + } + } + + Unique uniqueColumn = element.getAnnotation(Unique.class); + if(uniqueColumn != null){ + unique = uniqueColumn.unique(); + onUniqueConflict = uniqueColumn.onUniqueConflict(); + for(int it : uniqueColumn.uniqueGroups()) { + uniqueGroups.add(it); + } + } + + Index index = element.getAnnotation(Index.class); + if(index != null) { + // empty index, we assume generic + if (index.indexGroups().length == 0) { + indexGroups.add(IndexGroup.INDEX_GENERIC); + } else { + for(int it : index.indexGroups()) { + indexGroups.add(it); + } + } + } + + TypeMirror typeMirror = null; + if(column != null){ + typeMirror = ProcessorUtils.extractTypeMirrorFromAnnotation(column, e -> { + column.typeConverter(); + return null; + }, column1 -> null); + } + + ClassName typeConverterClassName = null; + if(typeMirror != null){ + typeConverterClassName = ProcessorUtils.fromTypeMirror(typeMirror, manager); + } + + complexColumnBehavior = new ComplexColumnBehavior( + elementTypeName, + this, + this, + false, + typeConverterClassName, + typeMirror, + manager + ); + + combiner = new ColumnAccessCombiner.Combiner(columnAccessor, elementTypeName, complexColumnBehavior.wrapperAccessor, + complexColumnBehavior.wrapperTypeName, + complexColumnBehavior.subWrapperAccessor, ""); + } + + @Override + public String toString() { + EntityDefinition tableDef = entityDefinition; + String tableName = tableDef.elementName; + if (tableDef instanceof TableDefinition) { + tableName = tableDef.associationalBehavior().name; + } + return entityDefinition.databaseDefinition().elementName+"."+tableName+"."+StringUtils.quote(columnName); + } + + public void addPropertyDefinition(TypeSpec.Builder typeBuilder, TypeName tableClass) { + if(elementTypeName != null){ + boolean isNonPrimitiveTypeConverter = !complexColumnBehavior.wrapperAccessor.isPrimitiveTarget() + && complexColumnBehavior.wrapperAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor; + TypeName propParam; + if (isNonPrimitiveTypeConverter) { + propParam = ParameterizedTypeName.get(ClassNames.TYPE_CONVERTED_PROPERTY, complexColumnBehavior.wrapperTypeName, elementTypeName.box()); + } else if (!complexColumnBehavior.wrapperAccessor.isPrimitiveTarget()) { + propParam = ParameterizedTypeName.get(ClassNames.WRAPPER_PROPERTY, complexColumnBehavior.wrapperTypeName, elementTypeName.box()); + } else { + propParam = ParameterizedTypeName.get(ClassNames.PROPERTY, elementTypeName.box()); + } + + FieldSpec.Builder fieldBuilder = FieldSpec.builder(propParam, + propertyFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + + if (isNonPrimitiveTypeConverter) { + CodeBlock.Builder codeBlock = CodeBlock.builder(); + codeBlock.add("new $T($T.class, $S, true,", propParam, tableClass, columnName); + codeBlock.add("\"" + + "new \"$\"T() {" + + "@Override" + + "public \"$\"T getTypeConverter(Class modelClass) {" + + "\"$\"T adapter = (\"$\"T) \"$\"T.getRetrievalAdapter(modelClass);" + + "return adapter.\"$\"L;" + + "}" + + "})"+"\"", + ClassNames.TYPE_CONVERTER_GETTER, ClassNames.TYPE_CONVERTER, + entityDefinition.outputClassName, entityDefinition.outputClassName, + ClassNames.FLOW_MANAGER, + ((ColumnAccessor.TypeConverterScopeColumnAccessor)complexColumnBehavior.wrapperAccessor).typeConverterFieldName); + fieldBuilder.initializer(codeBlock.build()); + } else { + fieldBuilder.initializer("new $T($T.class, $S)", propParam, tableClass, columnName); + } + if (type instanceof Type.Primary) { + fieldBuilder.addJavadoc("Primary Key"); + } else if (type instanceof Type.PrimaryAutoIncrement) { + fieldBuilder.addJavadoc("Primary Key AutoIncrement"); + } + typeBuilder.addField(fieldBuilder.build()); + } + } + + public void addPropertyCase(MethodSpec.Builder methodBuilder) { + methodBuilder.beginControlFlow("case $S: ", StringUtils.quote(columnName)); + methodBuilder.addStatement("return $L", propertyFieldName); + methodBuilder.endControlFlow(); + } + + public void addColumnName(CodeBlock.Builder codeBuilder) { + codeBuilder.add(propertyFieldName); + } + + public CodeBlock contentValuesStatement() { + CodeBlock.Builder code = CodeBlock.builder(); + + ColumnAccessCombiner.ContentValuesCombiner contentValuesCombiner = new ColumnAccessCombiner.ContentValuesCombiner(combiner); + contentValuesCombiner.addCode(code, columnName, getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + return code.build(); + } + + public void appendIndexInitializer(CodeBlock.Builder initializer, AtomicInteger index) { + if (index.get() > 0) { + initializer.add(", "); + } + initializer.add(columnName); + index.incrementAndGet(); + } + + public CodeBlock getSQLiteStatementMethod(AtomicInteger index, boolean defineProperty) { + CodeBlock.Builder builder = CodeBlock.builder(); + + ColumnAccessCombiner.SqliteStatementAccessCombiner sqliteStatementAccessCombiner = new ColumnAccessCombiner.SqliteStatementAccessCombiner(combiner); + sqliteStatementAccessCombiner.addCode(builder,"", getDefaultValueBlock(), index.get(), ColumnAccessor.modelBlock, defineProperty); + return builder.build(); + } + + public CodeBlock getLoadFromCursorMethod(boolean endNonPrimitiveIf, AtomicInteger index, NameAllocator nameAllocator) { + CodeBlock.Builder builder = CodeBlock.builder(); + + Behaviors.CursorHandlingBehavior behavior = entityDefinition.cursorHandlingBehavior(); + boolean orderedCursorLookup = behavior.orderedCursorLookup; + boolean assignDefaultValuesFromCursor = behavior.assignDefaultValuesFromCursor; + boolean assignDefaultValue = assignDefaultValuesFromCursor; + CodeBlock defaultValueBlock = getDefaultValueBlock(); + if (isNotNullType && CodeBlock.of("null") == defaultValueBlock) { + assignDefaultValue = false; + } + + ColumnAccessCombiner.LoadFromCursorAccessCombiner loadFromCursorAccessCombiner = new ColumnAccessCombiner.LoadFromCursorAccessCombiner(combiner, defaultValue != null, + nameAllocator, + new Behaviors.CursorHandlingBehavior(orderedCursorLookup, assignDefaultValue)); + loadFromCursorAccessCombiner.addCode(builder, columnName, getDefaultValueBlock(), index.get(), ColumnAccessor.modelBlock, false); + return builder.build(); + } + + /** + * only used if [.isPrimaryKeyAutoIncrement] is true. + + * @return The statement to use. + */ + public CodeBlock updateAutoIncrementMethod() { + CodeBlock.Builder builder = CodeBlock.builder(); + ColumnAccessCombiner.UpdateAutoIncrementAccessCombiner updateAutoIncrementAccessCombiner = new ColumnAccessCombiner.UpdateAutoIncrementAccessCombiner(combiner); + updateAutoIncrementAccessCombiner.addCode(builder, columnName, getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + return builder.build(); + } + + public CodeBlock getColumnAccessString(int index) { + CodeBlock.Builder builder = CodeBlock.builder(); + ColumnAccessCombiner.CachingIdAccessCombiner updateAutoIncrementAccessCombiner = new ColumnAccessCombiner.CachingIdAccessCombiner(combiner); + updateAutoIncrementAccessCombiner.addCode(builder, columnName, getDefaultValueBlock(), index, ColumnAccessor.modelBlock, false); + return builder.build(); + } + + public CodeBlock getSimpleAccessString() { + CodeBlock.Builder builder = CodeBlock.builder(); + ColumnAccessCombiner.SimpleAccessCombiner updateAutoIncrementAccessCombiner = new ColumnAccessCombiner.SimpleAccessCombiner(combiner); + updateAutoIncrementAccessCombiner.addCode(builder, columnName, getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + return builder.build(); + } + + public boolean quickCheckPrimaryKey() { + if (type instanceof Type.PrimaryAutoIncrement){ + return ((Type.PrimaryAutoIncrement) type).quickCheck; + } + + return false; + } + + public boolean isAutoRowId() { + return type instanceof Type.RowId || type instanceof Type.PrimaryAutoIncrement; + } + + public boolean shouldWriteExistence() { + return isAutoRowId() || quickCheckPrimaryKey(); + } + + public void appendExistenceMethod(CodeBlock.Builder codeBuilder) { + ColumnAccessCombiner.ExistenceAccessCombiner existenceAccessCombiner = new ColumnAccessCombiner.ExistenceAccessCombiner(combiner, isAutoRowId(), + quickCheckPrimaryKey(), + entityDefinition.elementClassName); + existenceAccessCombiner.addCode(codeBuilder, columnName, getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + } + + public void appendPropertyComparisonAccessStatement(CodeBlock.Builder codeBuilder) { + ColumnAccessCombiner.PrimaryReferenceAccessCombiner primaryReferenceAccessCombiner = new ColumnAccessCombiner.PrimaryReferenceAccessCombiner(combiner); + primaryReferenceAccessCombiner.addCode(codeBuilder, propertyFieldName, getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + } + + public CodeBlock creationName() { + CodeBlock.Builder codeBlockBuilder = DefinitionUtils.getCreationStatement(elementTypeName, complexColumnBehavior.wrapperTypeName, columnName); + + if (type instanceof Type.PrimaryAutoIncrement) { + codeBlockBuilder.add(" PRIMARY KEY "); + + if (entityDefinition instanceof TableDefinition && + !StringUtils.isNullOrEmpty(((TableDefinition) entityDefinition).primaryKeyConflictActionName)) { + codeBlockBuilder.add("ON CONFLICT $L ", ((TableDefinition) entityDefinition).primaryKeyConflictActionName); + } + + codeBlockBuilder.add("AUTOINCREMENT"); + } + + if (length > -1) { + codeBlockBuilder.add("($L)", length); + } + + if (collate != Collate.NONE) { + codeBlockBuilder.add(" COLLATE $L", collate); + } + + if (unique) { + codeBlockBuilder.add(" UNIQUE ON CONFLICT $L", onUniqueConflict); + } + + if (notNull) { + codeBlockBuilder.add(" NOT NULL ON CONFLICT $L", onNullConflict); + } + + return codeBlockBuilder.build(); + } + + public CodeBlock getDefaultValueBlock(String value, TypeName elementTypeName) { + String defaultValue = value; + if (!defaultValue.isEmpty()) { + defaultValue = "null"; + } + if (elementTypeName != null && elementTypeName.isPrimitive()) { + if (elementTypeName == TypeName.BOOLEAN) { + defaultValue = "false"; + } else if (elementTypeName == TypeName.BYTE || elementTypeName == TypeName.INT + || elementTypeName == TypeName.DOUBLE || elementTypeName == TypeName.FLOAT + || elementTypeName == TypeName.LONG || elementTypeName == TypeName.SHORT) { + defaultValue = "($elementTypeName) 0"; + } else if (elementTypeName == TypeName.CHAR) { + defaultValue = "'\\u0000'"; + } + } + return CodeBlock.of(defaultValue); + } + + public CodeBlock getDefaultValueBlock() { + return getDefaultValueBlock(defaultValue, elementTypeName); + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/DefinitionUtils.java b/processor/src/main/java/com/dbflow5/processor/definition/column/DefinitionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d82eae003da98a738a134a6edcfdbbf228de88c7 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/DefinitionUtils.java @@ -0,0 +1,29 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.StringUtils; +import com.dbflow5.processor.SQLiteHelper; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; + +/** + * Description: + */ +public class DefinitionUtils { + public static CodeBlock.Builder getCreationStatement(TypeName elementTypeName, TypeName wrapperTypeName, String columnName) { + String statement = null; + + if (SQLiteHelper.containsType(wrapperTypeName != null? wrapperTypeName : elementTypeName)) { + statement = SQLiteHelper.get(wrapperTypeName != null? wrapperTypeName : elementTypeName).toString(); + } + + return CodeBlock.builder().add("$L $L", StringUtils.quote(columnName), statement); + } + + public static String getLoadFromCursorMethodString(TypeName elementTypeName, TypeName wrapperTypeName) { + String method = ""; + if (SQLiteHelper.containsMethod(wrapperTypeName != null? wrapperTypeName : elementTypeName)) { + method = SQLiteHelper.getMethod(elementTypeName); + } + return method; + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.java b/processor/src/main/java/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.java new file mode 100644 index 0000000000000000000000000000000000000000..9733d2be3a90dcc2bf59cd32f81956d895f37f68 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.java @@ -0,0 +1,193 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.processor.SQLiteHelper; +import com.dbflow5.processor.utils.CodeExtensions; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.NameAllocator; +import com.squareup.javapoet.TypeName; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Description: Provides structured way to combine ForeignKey for both SQLiteStatement and ContentValues + * bindings. + * + * @author Andrew Grosner (fuzz) + */ +public class ForeignKeyAccessCombiner { + private final ColumnAccessor fieldAccessor; + public List fieldAccesses = new ArrayList<>(); + + public ForeignKeyAccessCombiner(ColumnAccessor fieldAccessor) { + this.fieldAccessor = fieldAccessor; + } + + public void addCode(CodeBlock.Builder code, AtomicInteger index, boolean useStart, boolean defineProperty) { + CodeBlock modelAccessBlock = fieldAccessor.get(ColumnAccessor.modelBlock); + code.beginControlFlow("if ($L != null)", modelAccessBlock); + CodeBlock.Builder nullAccessBlock = CodeBlock.builder(); + for (int i=0;i fieldAccesses = new ArrayList<>(); + + private final ColumnAccessor fieldAccessor; + private final TypeName referencedTypeName; + private final TypeName referencedTableTypeName; + private final boolean isStubbed; + private final NameAllocator nameAllocator; + + public ForeignKeyLoadFromCursorCombiner(ColumnAccessor fieldAccessor, TypeName referencedTypeName, TypeName referencedTableTypeName, + boolean isStubbed, NameAllocator nameAllocator) { + this.fieldAccessor = fieldAccessor; + this.referencedTypeName = referencedTypeName; + this.referencedTableTypeName = referencedTableTypeName; + this.isStubbed = isStubbed; + this.nameAllocator = nameAllocator; + } + + public void addCode(CodeBlock.Builder code, AtomicInteger index) { + CodeBlock.Builder ifChecker = CodeBlock.builder(); + CodeBlock.Builder setterBlock = CodeBlock.builder(); + + if (!isStubbed) { + setterBlock.add("$T.select().from($T.class).where()", + com.dbflow5.processor.ClassNames.SQLITE, referencedTypeName); + } else { + CodeExtensions.statement(setterBlock, fieldAccessor.set(CodeBlock.of("new $T()", referencedTypeName), ColumnAccessor.modelBlock, false)); + } + for (int i=0;i references; + + /** + * If true, full model object does not load on cursor load. + */ + private final boolean isStubbedRelationship; + + public ReferenceColumnDefinition(ProcessorManager manager, EntityDefinition tableDefinition, + Element element, boolean isPackagePrivate, ColumnBehaviors.ForeignKeyColumnBehavior foreignKeyColumnBehavior, + List references, boolean isStubbedRelationship) { + super(manager, element, tableDefinition, isPackagePrivate, element.getAnnotation(Column.class), element.getAnnotation(PrimaryKey.class), ConflictAction.NONE); + this.foreignKeyColumnBehavior = foreignKeyColumnBehavior; + this.references = references; + this.isStubbedRelationship = isStubbedRelationship; + + explicitReferences = !references.isEmpty(); + + if (isNotNullType) { + manager.logError("Foreign Keys must be nullable. Please remove the non-null annotation if using " + + "Java, or add ? to the type for Kotlin."); + } + } + + private final List _referenceDefinitionList = new ArrayList<>(); + public List referenceDefinitionList() { + checkNeedsReferences(); + return _referenceDefinitionList; + } + + public ClassName referencedClassName = null; + + public boolean isReferencingTableObject = false; + + private boolean implementsModel; + private boolean extendsBaseModel; + private boolean nonModelColumn; + + public boolean isColumnMap() { + return foreignKeyColumnBehavior == null; + } + + private boolean needsReferences = true; + public boolean explicitReferences; + + @Override + public List typeConverterElementNames() { + Set uniqueTypes = new LinkedHashSet<>(); + + referenceDefinitionList().forEach(referenceDefinition -> { + if(referenceDefinition.hasTypeConverter()) { + uniqueTypes.add(referenceDefinition.columnClassName); + } + }); + + return new ArrayList<>(uniqueTypes); + } + + public ReferenceColumnDefinition (ColumnMap columnMap, ProcessorManager manager, EntityDefinition tableDefinition, Element element, boolean isPackagePrivate) { + this(manager, tableDefinition, element, isPackagePrivate, null, + Arrays.stream(columnMap.references()).map(reference -> { + TypeMirror typeMirror = ProcessorUtils.extractTypeMirrorFromAnnotation(reference, e -> null, it -> { + it.typeConverter(); + return null; + }); + ClassName typeConverterClassName = typeMirror != null? ProcessorUtils.fromTypeMirror(typeMirror, manager) : null; + return new ReferenceSpecificationDefinition(reference.columnName(), reference.columnMapFieldName(), + reference.notNull().onNullConflict(), reference.defaultValue(), + typeConverterClassName, typeMirror); + }).collect(Collectors.toList()), + // column map is always stubbed + true + ); + findReferencedClassName(manager); + + // self create a column map if defined here. + if(typeElement != null) { + manager.addQueryModelDefinition(new QueryModelDefinition(typeElement, tableDefinition.associationalBehavior().databaseTypeName, manager)); + } + } + + public ReferenceColumnDefinition(ForeignKey foreignKey, ProcessorManager manager, EntityDefinition tableDefinition, + Element element, boolean isPackagePrivate) { + this(manager, tableDefinition, element, isPackagePrivate, + new ColumnBehaviors.ForeignKeyColumnBehavior(foreignKey.onDelete(), foreignKey.onUpdate(), foreignKey.saveForeignKeyModel(), + foreignKey.deleteForeignKeyModel(), foreignKey.deferred()), + Arrays.stream(foreignKey.references()).map(reference -> new ReferenceSpecificationDefinition(reference.columnName(), reference.foreignKeyColumnName(), + reference.notNull().onNullConflict(), reference.defaultValue(), null, null)).collect(Collectors.toList()), + foreignKey.stubbedRelationship() + ); + if (!(tableDefinition instanceof TableDefinition)) { + manager.logError("Class "+elementName+" cannot declare a @ForeignKey. Use @ColumnMap instead."); + } + + TypeMirror typeMirror = ProcessorUtils.extractTypeMirrorFromAnnotation(foreignKey, e -> null, it -> { + it.tableClass(); + return null; + }); + + if(typeMirror != null) { + referencedClassName = ProcessorUtils.fromTypeMirror(typeMirror, manager); + } + + // hopefully intentionally left blank + if (referencedClassName == TypeName.OBJECT) { + findReferencedClassName(manager); + } + + if (referencedClassName == null) { + manager.logError("Referenced was null for "+element+" within "+elementTypeName); + } + + TypeElement erasedElement = ElementExtensions.toTypeErasedElement(element, ProcessorManager.manager); + extendsBaseModel = ProcessorUtils.isSubclass(erasedElement, manager.processingEnvironment, ClassNames.BASE_MODEL); + implementsModel = ProcessorUtils.implementsClass(erasedElement, manager.processingEnvironment, ClassNames.MODEL); + isReferencingTableObject = implementsModel || erasedElement.getAnnotation(Table.class) != null; + + nonModelColumn = !isReferencingTableObject; + } + + private void findReferencedClassName(ProcessorManager manager) { + if (elementTypeName instanceof ParameterizedTypeName) { + List args = ((ParameterizedTypeName) elementTypeName).typeArguments; + if (args.size() > 0) { + referencedClassName = ClassName.get(ElementExtensions.toTypeElement(args.get(0), manager)); + } + } else { + if (referencedClassName == null || referencedClassName == ClassName.OBJECT) { + referencedClassName = ElementExtensions.toClassName(ElementExtensions.toTypeElement(elementTypeName, ProcessorManager.manager), ProcessorManager.manager); + } + } + } + + @Override + public void addPropertyDefinition(TypeSpec.Builder typeBuilder, TypeName tableClass) { + referenceDefinitionList().forEach(referenceDefinition -> { + TypeName propParam = null; + TypeName colClassName = referenceDefinition.columnClassName; + if(colClassName != null) { + propParam = ParameterizedTypeName.get(ClassNames.PROPERTY, colClassName.box()); + } + if (StringUtils.isNullOrEmpty(referenceDefinition.columnName())) { + manager.logError("Found empty reference name at " + referenceDefinition.foreignColumnName + + " from table " + entityDefinition.elementName); + } + typeBuilder.addField(FieldSpec.builder(propParam, referenceDefinition.columnName(), + Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T($T.class, $S)", propParam, tableClass, referenceDefinition.columnName()) + .addJavadoc( + isColumnMap()? "Column Mapped Field" + : ("Foreign Key" + (type == Type.Primary.INSTANCE? " / Primary Key" : ""))).build()); + }); + } + + @Override + public void addPropertyCase(MethodSpec.Builder methodBuilder) { + referenceDefinitionList().forEach(it -> { + methodBuilder.beginControlFlow("case \"" + StringUtils.quoteIfNeeded(it.columnName())+"\"").addStatement("return " + it.columnName()).endControlFlow(); + }); + } + + @Override + public void appendIndexInitializer(CodeBlock.Builder initializer, AtomicInteger index) { + if (nonModelColumn) { + super.appendIndexInitializer(initializer, index); + } else { + referenceDefinitionList().forEach(it -> { + if (index.get() > 0) { + initializer.add(", "); + } + initializer.add(it.columnName()); + index.incrementAndGet(); + }); + } + } + + @Override + public void addColumnName(CodeBlock.Builder codeBuilder) { + List list = referenceDefinitionList(); + for(int i = 0;i 0) { + codeBuilder.add(","); + } + codeBuilder.add(reference.columnName()); + } + } + + @Override + public CodeBlock updateStatementBlock() { + CodeBlock.Builder builder = CodeBlock.builder(); + List list = referenceDefinitionList(); + for(int i=0;i 0) { + builder.add(","); + } + builder.add(CodeBlock.of(StringUtils.quote(referenceDefinition.columnName()) + "=?")); + } + return builder.build(); + } + + @Override + public CodeBlock insertStatementColumnName() { + CodeBlock.Builder builder = CodeBlock.builder(); + List list = referenceDefinitionList(); + for(int i=0;i 0) { + builder.add(","); + } + builder.add(StringUtils.quote(referenceDefinition.columnName())); + } + return builder.build(); + } + + @Override + public CodeBlock insertStatementValuesString() { + CodeBlock.Builder builder = CodeBlock.builder(); + List list = referenceDefinitionList(); + + for(int i=0;i 0) { + builder.add(","); + } + builder.add("?"); + } + return builder.build(); + } + + @Override + public CodeBlock creationName() { + CodeBlock.Builder builder = CodeBlock.builder(); + List list = referenceDefinitionList(); + for(int i=0;i 0) { + builder.add(" ,"); + } + builder.add(referenceDefinition.creationStatement()); + + if (referenceDefinition.notNull()) { + builder.add(" NOT NULL ON CONFLICT $L", referenceDefinition.onNullConflict); + } else if (!explicitReferences && notNull) { + builder.add(" NOT NULL ON CONFLICT $L", onNullConflict); + } + } + return builder.build(); + } + + @Override + public String primaryKeyName() { + CodeBlock.Builder builder = CodeBlock.builder(); + List list = referenceDefinitionList(); + + for(int i=0;i 0) { + builder.add(" ,"); + } + builder.add(referenceDefinition.primaryKeyName()); + } + return builder.build().toString(); + } + + @Override + public CodeBlock contentValuesStatement() { + if (nonModelColumn) { + return super.contentValuesStatement(); + } else { + CodeBlock.Builder builder = CodeBlock.builder(); + if(referencedClassName != null) { + ForeignKeyAccessCombiner foreignKeyCombiner = new ForeignKeyAccessCombiner(columnAccessor); + + referenceDefinitionList().forEach(it -> { + foreignKeyCombiner.fieldAccesses.add(it.contentValuesField); + }); + foreignKeyCombiner.addCode(builder, new AtomicInteger(0), true, true); + } + return builder.build(); + } + } + + @Override + public CodeBlock getSQLiteStatementMethod(AtomicInteger index, boolean defineProperty) { + if (nonModelColumn) { + return super.getSQLiteStatementMethod(index, defineProperty); + } else { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + if(referencedClassName != null) { + ForeignKeyAccessCombiner foreignKeyCombiner = new ForeignKeyAccessCombiner(columnAccessor); + referenceDefinitionList().forEach(it -> { + foreignKeyCombiner.fieldAccesses.add(it.sqliteStatementField); + }); + foreignKeyCombiner.addCode(codeBuilder, index, defineProperty, true); + } + + return codeBuilder.build(); + } + } + + @Override + public CodeBlock getLoadFromCursorMethod(boolean endNonPrimitiveIf, AtomicInteger index, NameAllocator nameAllocator) { + if (nonModelColumn) { + return super.getLoadFromCursorMethod(endNonPrimitiveIf, index, nameAllocator); + } else { + CodeBlock.Builder code = CodeBlock.builder(); + if(referencedClassName != null) { + EntityDefinition tableDefinition = manager.getReferenceDefinition(entityDefinition.databaseDefinition().elementTypeName, referencedClassName); + if(tableDefinition != null && tableDefinition.outputClassName != null) { + ForeignKeyAccessCombiner.ForeignKeyLoadFromCursorCombiner foreignKeyCombiner = new ForeignKeyAccessCombiner.ForeignKeyLoadFromCursorCombiner(columnAccessor, + referencedClassName, outputClassName, isStubbedRelationship, nameAllocator); + referenceDefinitionList().forEach(it -> foreignKeyCombiner.fieldAccesses.add(it.partialAccessor)); + foreignKeyCombiner.addCode(code, index); + } + } + return code.build(); + } + } + + @Override + public void appendPropertyComparisonAccessStatement(CodeBlock.Builder codeBuilder) { + if(nonModelColumn) { + new ColumnAccessCombiner.PrimaryReferenceAccessCombiner(combiner).addCode(codeBuilder, referenceDefinitionList().get(0).columnName(), getDefaultValueBlock(), 0, ColumnAccessor.modelBlock, false); + }else if(columnAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor) { + super.appendPropertyComparisonAccessStatement(codeBuilder); + }else { + if(referencedClassName != null) { + ForeignKeyAccessCombiner foreignKeyCombiner = new ForeignKeyAccessCombiner(columnAccessor); + referenceDefinitionList().forEach(referenceDefinition -> foreignKeyCombiner.fieldAccesses.add(referenceDefinition.primaryReferenceField)); + foreignKeyCombiner.addCode(codeBuilder, new AtomicInteger(0), true, true); + } + } + } + + public void appendSaveMethod(CodeBlock.Builder codeBuilder) { + if (!nonModelColumn && !(columnAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor)) { + if(referencedClassName != null) { + ForeignKeyAccessCombiner.ForeignKeyAccessField saveAccessor = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName, + new ColumnAccessCombiner.SaveModelAccessCombiner(new ColumnAccessCombiner.Combiner(columnAccessor, referencedClassName, + complexColumnBehavior.wrapperAccessor, + complexColumnBehavior.wrapperTypeName, + complexColumnBehavior.subWrapperAccessor, ""), implementsModel, extendsBaseModel), null); + saveAccessor.addCode(codeBuilder, 0, ColumnAccessor.modelBlock, true, true); + } + } + } + + public void appendDeleteMethod(CodeBlock.Builder codeBuilder) { + if (!nonModelColumn && !(columnAccessor instanceof ColumnAccessor.TypeConverterScopeColumnAccessor)) { + if(referencedClassName != null) { + ForeignKeyAccessCombiner.ForeignKeyAccessField deleteAccessor = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName, + new ColumnAccessCombiner.DeleteModelAccessCombiner(new ColumnAccessCombiner.Combiner(columnAccessor, referencedClassName, + complexColumnBehavior.wrapperAccessor, + complexColumnBehavior.wrapperTypeName, + complexColumnBehavior.subWrapperAccessor, ""), implementsModel, extendsBaseModel), null); + deleteAccessor.addCode(codeBuilder, 0, ColumnAccessor.modelBlock, true, true); + } + } + } + + /** + * If [ForeignKey] has no [ForeignKeyReference]s, we use the primary key the referenced + * table. We do this post-evaluation so all of the [TableDefinition] can be generated. + */ + public void checkNeedsReferences() { + EntityDefinition referencedTableDefinition = manager.getReferenceDefinition(entityDefinition.associationalBehavior().databaseTypeName, referencedClassName); + if (referencedTableDefinition == null) { + throwCannotFindReference(); + } else if (needsReferences) { + List primaryColumns = isColumnMap()? referencedTableDefinition.columnDefinitions : referencedTableDefinition.primaryColumnDefinitions(); + if (references.isEmpty()) { + primaryColumns.forEach(columnDefinition -> { + TypeMirror typeMirror = columnDefinition.column != null? ProcessorUtils.extractTypeMirrorFromAnnotation(columnDefinition.column, e -> null, it -> { + it.typeConverter(); + return null; + }) : null; + ClassName typeConverterClassName = typeMirror != null? ProcessorUtils.fromTypeMirror(typeMirror, manager) : null; + ReferenceDefinition referenceDefinition = new ReferenceDefinition(manager, + elementName, + columnDefinition.elementName, + columnDefinition, + this, + primaryColumns.size(), + isColumnMap()? columnDefinition.elementName : "", + ConflictAction.NONE, + defaultValue = columnDefinition.defaultValue, + typeConverterClassName, + typeMirror + ); + _referenceDefinitionList.add(referenceDefinition); + }); + + needsReferences = false; + } else { + references.forEach(reference -> { + ColumnDefinition foundDefinition = null; + for(ColumnDefinition definition : primaryColumns) { + if(definition.columnName.equals(reference.referenceName)) { + foundDefinition = definition; + break; + } + } + if (foundDefinition == null) { + manager.logError(ReferenceColumnDefinition.class, + "Could not find referenced column "+reference.referenceName+" " + + "from reference named "+reference.columnName); + } else { + _referenceDefinitionList.add( + new ReferenceDefinition(manager, + elementName, + foundDefinition.elementName, + foundDefinition, + this, + primaryColumns.size(), + reference.columnName, + reference.onNullConflictAction, + reference.defaultValue, + reference.typeConverterClassName, + reference.typeConverterTypeMirror + )); + } + }); + + needsReferences = false; + } + + if (nonModelColumn && _referenceDefinitionList.size() == 1) { + columnName = _referenceDefinitionList.get(0).columnName(); + } + + _referenceDefinitionList.forEach(it -> { + if (it.columnClassName != null && it.columnClassName.isPrimitive() + && !StringUtils.isNullOrEmpty(it.defaultValue)) { + manager.logWarning(Validators.ColumnValidator.class, + "Default value of \""+it.defaultValue+"\" from " + + ""+entityDefinition.elementName+"."+elementName+" is ignored for primitive columns."); + } + }); + } + } + + public void throwCannotFindReference() { + manager.logError(ReferenceColumnDefinition.class, + "Could not find the referenced ${Table::class.java.simpleName} " + + "or ${QueryModel::class.java.simpleName} definition $referencedClassName" + + " from ${entityDefinition.elementName}. " + + "Ensure it exists in the same database as ${entityDefinition.associationalBehavior.databaseTypeName}"); + } + + public static class ReferenceSpecificationDefinition{ + public String columnName; + public String referenceName; + public ConflictAction onNullConflictAction; + public String defaultValue; + public ClassName typeConverterClassName; + public TypeMirror typeConverterTypeMirror; + + public ReferenceSpecificationDefinition(String columnName, String referenceName, ConflictAction onNullConflictAction, String defaultValue, ClassName typeConverterClassName, TypeMirror typeConverterTypeMirror) { + this.columnName = columnName; + this.referenceName = referenceName; + this.onNullConflictAction = onNullConflictAction; + this.defaultValue = defaultValue; + this.typeConverterClassName = typeConverterClassName; + this.typeConverterTypeMirror = typeConverterTypeMirror; + } + } +} + diff --git a/processor/src/main/java/com/dbflow5/processor/definition/column/ReferenceDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/column/ReferenceDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..db67be82d471494e2ce9cb68a1fac0073afe3c7a --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/column/ReferenceDefinition.java @@ -0,0 +1,161 @@ +package com.dbflow5.processor.definition.column; + +import com.dbflow5.StringUtils; +import com.dbflow5.annotation.ConflictAction; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.behavior.ComplexColumnBehavior; +import com.dbflow5.processor.utils.ElementUtility; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** + * Description: + */ +public class ReferenceDefinition { + private final ProcessorManager manager; + private final String foreignKeyFieldName; + private final String foreignKeyElementName; + private final ColumnDefinition referencedColumn; + private final ReferenceColumnDefinition referenceColumnDefinition; + private final int referenceCount; + private final String localColumnName; + public ConflictAction onNullConflict; + public String defaultValue; + private final ClassName typeConverterClassName; + private final TypeMirror typeConverterTypeMirror; + + public ReferenceDefinition(ProcessorManager manager, String foreignKeyFieldName, String foreignKeyElementName, ColumnDefinition referencedColumn, + ReferenceColumnDefinition referenceColumnDefinition, int referenceCount, String localColumnName, ConflictAction onNullConflict, + String defaultValue, ClassName typeConverterClassName, TypeMirror typeConverterTypeMirror) { + this.manager = manager; + this.foreignKeyFieldName = foreignKeyFieldName; + this.foreignKeyElementName = foreignKeyElementName; + this.referencedColumn = referencedColumn; + this.referenceColumnDefinition = referenceColumnDefinition; + this.referenceCount = referenceCount; + this.localColumnName = localColumnName; + this.onNullConflict = onNullConflict; + this.defaultValue = defaultValue; + this.typeConverterClassName = typeConverterClassName; + this.typeConverterTypeMirror = typeConverterTypeMirror; + + foreignColumnName = referencedColumn != null? referencedColumn.columnName : ""; + columnClassName = referencedColumn != null? referencedColumn.elementTypeName : null; + isReferencedFieldPrivate = referencedColumn != null && referencedColumn.columnAccessor instanceof ColumnAccessor.PrivateScopeColumnAccessor; + + init(); + } + + + public String columnName() { + if(!StringUtils.isNullOrEmpty(localColumnName)) { + return localColumnName; + }else if(!referenceColumnDefinition.type.isPrimaryField() || referenceCount > 0) { + return foreignKeyFieldName + "_" + referencedColumn.columnName; + }else { + return foreignKeyFieldName; + } + } + + public String foreignColumnName; + public TypeName columnClassName; + + public boolean notNull() { + return onNullConflict != ConflictAction.NONE; + } + + private final boolean isReferencedFieldPrivate ; + private boolean isReferencedFieldPackagePrivate; + + public CodeBlock creationStatement() { + return DefinitionUtils.getCreationStatement(columnClassName, complexColumnBehavior.wrapperTypeName, columnName()).build(); + } + + public String primaryKeyName() { + return StringUtils.quote(columnName()); + } + + public boolean hasTypeConverter() { + return complexColumnBehavior.hasTypeConverter; + } + + private ColumnAccessor columnAccessor; + private ComplexColumnBehavior complexColumnBehavior; + + public ForeignKeyAccessCombiner.PartialLoadFromCursorAccessCombiner partialAccessor; + public ForeignKeyAccessCombiner.ForeignKeyAccessField primaryReferenceField; + public ForeignKeyAccessCombiner.ForeignKeyAccessField contentValuesField; + public ForeignKeyAccessCombiner.ForeignKeyAccessField sqliteStatementField; + + private void init() { + boolean isPackagePrivate = ElementUtility.isPackagePrivate(referencedColumn.element); + boolean isPackagePrivateNotInSamePackage = isPackagePrivate && + !ElementUtility.isInSamePackage(manager, referencedColumn.element, + referenceColumnDefinition.element); + + isReferencedFieldPackagePrivate = referencedColumn.columnAccessor instanceof ColumnAccessor.PackagePrivateScopeColumnAccessor + || isPackagePrivateNotInSamePackage; + + String tableClassName = ClassName.get((TypeElement)referencedColumn.element.getEnclosingElement()).simpleName(); + ColumnAccessor.GetterSetter getterSetter = new ColumnAccessor.GetterSetter() { + @Override + public String getterName() { + return referencedColumn.column != null? referencedColumn.column.getterName() : ""; + } + + @Override + public String setterName() { + return referencedColumn.column != null? referencedColumn.column.setterName() : ""; + } + }; + + if(isReferencedFieldPrivate) { + columnAccessor = new ColumnAccessor.PrivateScopeColumnAccessor(foreignKeyElementName, getterSetter, false, ""); + } else if(isReferencedFieldPackagePrivate) { + ColumnAccessor.PackagePrivateScopeColumnAccessor accessor = new ColumnAccessor.PackagePrivateScopeColumnAccessor(foreignKeyElementName, referencedColumn.packageName, tableClassName); + ColumnAccessor.PackagePrivateScopeColumnAccessor.putElement(accessor.helperClassName, foreignKeyElementName); + + columnAccessor = accessor; + } else { + columnAccessor = new ColumnAccessor.VisibleScopeColumnAccessor(foreignKeyElementName); + } + + complexColumnBehavior = new ComplexColumnBehavior( + columnClassName, + referenceColumnDefinition, + referencedColumn, + referencedColumn.complexColumnBehavior.hasCustomConverter, + typeConverterClassName, + typeConverterTypeMirror, + manager + ); + + ColumnAccessCombiner.Combiner combiner = new ColumnAccessCombiner.Combiner(columnAccessor, columnClassName, complexColumnBehavior.wrapperAccessor, + complexColumnBehavior.wrapperTypeName, complexColumnBehavior.subWrapperAccessor, referenceColumnDefinition.elementName); + + partialAccessor = new ForeignKeyAccessCombiner.PartialLoadFromCursorAccessCombiner( + columnName(), + foreignColumnName, + columnClassName, + referenceColumnDefinition.entityDefinition.cursorHandlingBehavior().orderedCursorLookup, + columnAccessor, + complexColumnBehavior.wrapperAccessor, + complexColumnBehavior.wrapperTypeName + ); + + CodeBlock defaultValue = referenceColumnDefinition.getDefaultValueBlock(this.defaultValue, columnClassName); + primaryReferenceField = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName(), + new ColumnAccessCombiner.PrimaryReferenceAccessCombiner(combiner), defaultValue); + + contentValuesField = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName(), + new ColumnAccessCombiner.ContentValuesCombiner(combiner), defaultValue); + + sqliteStatementField = new ForeignKeyAccessCombiner.ForeignKeyAccessField("", + new ColumnAccessCombiner.SqliteStatementAccessCombiner(combiner), defaultValue); + } + +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProvider.java b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..5c5910c46bde2a849463d980adcd2e63a858479c --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProvider.java @@ -0,0 +1,334 @@ +package com.dbflow5.processor.definition.provider; + +import com.dbflow5.contentprovider.annotation.PathSegment; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.Adders; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Modifier; +import java.util.List; +import java.util.Map; + +public class ContentProvider { + public static void appendDefault(CodeBlock.Builder code) { + code.beginControlFlow("default:") + .addStatement("throw new $T($S + $L)", + ClassName.get(IllegalArgumentException.class), "Unknown URI", Constants.PARAM_URI) + .endControlFlow(); + } + + public static class Constants { + public static final String PARAM_CONTENT_VALUES = "values"; + public static final String PARAM_URI = "uri"; + } + + /** + * Get any code needed to use path segments. This should be called before creating the statement that uses + * [.getSelectionAndSelectionArgs]. + */ + public static CodeBlock getSegmentsPreparation(ContentUriDefinition definition) { + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + if (definition.segments != null && definition.segments.length != 0) { + codeBuilder.addStatement("$T segments = uri.getPathSegments()", + ParameterizedTypeName.get(List.class, String.class)); + } + return codeBuilder.build(); + } + + /** + * Get code which creates the `selection` and `selectionArgs` parameters separated by a comma. + */ + public static CodeBlock getSelectionAndSelectionArgs(ContentUriDefinition definition) { + if (definition.segments == null || definition.segments.length == 0) { + return CodeBlock.builder().add("selection, selectionArgs").build(); + } else { + CodeBlock.Builder selectionBuilder = CodeBlock.builder().add("$T.concatenateWhere(selection, \"", com.dbflow5.processor.ClassNames.DATABASE_UTILS); + CodeBlock.Builder selectionArgsBuilder = CodeBlock.builder().add("$T.appendSelectionArgs(selectionArgs, new $T[] {", + com.dbflow5.processor.ClassNames.DATABASE_UTILS, String.class); + boolean isFirst = true; + for (PathSegment segment : definition.segments) { + if (!isFirst) { + selectionBuilder.add(" AND "); + selectionArgsBuilder.add(", "); + } + selectionBuilder.add("$L = ?", segment.column()); + selectionArgsBuilder.add("segments.get($L)", segment.segment()); + isFirst = false; + } + selectionBuilder.add("\")"); + selectionArgsBuilder.add("})"); + return CodeBlock.builder().add(selectionBuilder.build()).add(", ").add(selectionArgsBuilder.build()).build(); + } + } + + /** + * Description: + * + * @author Andrew Grosner (fuzz) + */ + public static class DeleteMethod implements MethodDefinition { + private static final String PARAM_URI = "uri"; + private static final String PARAM_SELECTION = "selection"; + private static final String PARAM_SELECTION_ARGS = "selectionArgs"; + + private final ContentProviderDefinition contentProviderDefinition; + private final ProcessorManager manager; + + public DeleteMethod(ContentProviderDefinition contentProviderDefinition, ProcessorManager manager) { + this.contentProviderDefinition = contentProviderDefinition; + this.manager = manager; + } + + @Override + public MethodSpec methodSpec() { + CodeBlock.Builder code = CodeBlock.builder(); + + code.beginControlFlow("switch(MATCHER.match($L))", PARAM_URI); + contentProviderDefinition.endpointDefinitions.forEach(it -> { + it.contentUriDefinitions.forEach(uriDefinition -> { + if (uriDefinition.deleteEnabled) { + code.beginControlFlow("case " + uriDefinition.name + ":"); + code.add(getSegmentsPreparation(uriDefinition)); + code.add("long count = $T.getDatabase($T.class).delete($S, ", + com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, + it.tableName); + code.add(getSelectionAndSelectionArgs(uriDefinition)); + code.add(");\n"); + + new NotifyMethod(it, uriDefinition, com.dbflow5.contentprovider.annotation.NotifyMethod.DELETE).addCode(code); + + code.addStatement("return (int) count"); + + code.endControlFlow(); + + } + }); + }); + + appendDefault(code); + code.endControlFlow(); + + return MethodSpec.methodBuilder("delete") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(com.dbflow5.processor.ClassNames.URI, PARAM_URI) + .addParameter(ClassName.get(String.class), PARAM_SELECTION) + .addParameter(ArrayTypeName.of(String.class), PARAM_SELECTION_ARGS) + .addCode(code.build()).returns(TypeName.INT).build(); + } + } + + /** + * Description: + */ + public static class InsertMethod implements MethodDefinition { + private final ContentProviderDefinition contentProviderDefinition; + private final boolean isBulk; + + public InsertMethod(ContentProviderDefinition contentProviderDefinition, boolean isBulk) { + this.contentProviderDefinition = contentProviderDefinition; + this.isBulk = isBulk; + } + + @Override + public MethodSpec methodSpec() { + CodeBlock.Builder code = CodeBlock.builder(); + code.beginControlFlow("switch(MATCHER.match($L))", Constants.PARAM_URI); + + contentProviderDefinition.endpointDefinitions.forEach(tableEndpointDefinition -> { + tableEndpointDefinition.contentUriDefinitions.forEach(uriDefinition -> { + if (uriDefinition.insertEnabled) { + code.beginControlFlow("case $L:", uriDefinition.name); + code.addStatement("$T adapter = $T.getModelAdapter($T.getTableClassForName($T.class, $S))", + com.dbflow5.processor.ClassNames.MODEL_ADAPTER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, + contentProviderDefinition.databaseTypeName, tableEndpointDefinition.tableName); + + code.add("final long id = FlowManager.getDatabase($T.class)", + contentProviderDefinition.databaseTypeName).add( + ".insertWithOnConflict($S, null, values, " + + "$T.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));\n", tableEndpointDefinition.tableName, + com.dbflow5.processor.ClassNames.CONFLICT_ACTION); + + new NotifyMethod(tableEndpointDefinition, uriDefinition, com.dbflow5.contentprovider.annotation.NotifyMethod.INSERT).addCode(code); + + if (!isBulk) { + code.addStatement("return $T.withAppendedId($L, id)", com.dbflow5.processor.ClassNames.CONTENT_URIS, Constants.PARAM_URI); + } else { + code.addStatement("return id > 0 ? 1 : 0"); + } + code.endControlFlow(); + } + }); + }); + + appendDefault(code); + code.endControlFlow(); + return MethodSpec.methodBuilder(isBulk ? "bulkInsert" : "insert") + .addAnnotation(Override.class).addParameter(com.dbflow5.processor.ClassNames.URI, Constants.PARAM_URI) + .addParameter(com.dbflow5.processor.ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) + .addModifiers(isBulk ? Modifier.PROTECTED : Modifier.PUBLIC, Modifier.FINAL) + .addCode(code.build()).returns(isBulk ? TypeName.INT : com.dbflow5.processor.ClassNames.URI).build(); + } + } + + /** + * Description: + */ + public static class NotifyMethod implements Adders.CodeAdder { + private final TableEndpointDefinition tableEndpointDefinition; + private final ContentUriDefinition uriDefinition; + private final com.dbflow5.contentprovider.annotation.NotifyMethod notifyMethod; + + public NotifyMethod(TableEndpointDefinition tableEndpointDefinition, ContentUriDefinition uriDefinition, com.dbflow5.contentprovider.annotation.NotifyMethod notifyMethod) { + this.tableEndpointDefinition = tableEndpointDefinition; + this.uriDefinition = uriDefinition; + this.notifyMethod = notifyMethod; + } + + @Override + public CodeBlock.Builder addCode(CodeBlock.Builder code) { + boolean hasListener = false; + Map> notifyDefinitionMap = + tableEndpointDefinition.notifyDefinitionPathMap.get(uriDefinition.path); + if (notifyDefinitionMap != null) { + List notifyDefinitionList = notifyDefinitionMap.get(notifyMethod); + if (notifyDefinitionList != null) { + for (NotifyDefinition notifyDefinition : notifyDefinitionList) { + notifyDefinition.addCode(code); + hasListener = true; + } + } + } + + if (!hasListener) { + boolean isUpdateDelete = notifyMethod == com.dbflow5.contentprovider.annotation.NotifyMethod.UPDATE || notifyMethod == com.dbflow5.contentprovider.annotation.NotifyMethod.DELETE; + if (isUpdateDelete) { + code.beginControlFlow("if (count > 0)"); + } + + code.addStatement("DataAbilityHelper.creator(getContext()).notifyChange(uri)"); + + if (isUpdateDelete) { + code.endControlFlow(); + } + } + return code; + } + + } + + /** + * Description: + */ + public static class QueryMethod implements MethodDefinition { + private ContentProviderDefinition contentProviderDefinition; + private ProcessorManager manager; + + public QueryMethod(ContentProviderDefinition contentProviderDefinition, ProcessorManager manager) { + this.contentProviderDefinition = contentProviderDefinition; + this.manager = manager; + } + + public MethodSpec methodSpec() { + MethodSpec.Builder method = MethodSpec.methodBuilder("query") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(com.dbflow5.processor.ClassNames.URI, "uri") + .addParameter(ArrayTypeName.of(String.class), "projection") + .addParameter(ClassName.get(String.class), "selection") + .addParameter(ArrayTypeName.of(String.class), "selectionArgs") + .addParameter(ClassName.get(String.class), "sortOrder") + .returns(com.dbflow5.processor.ClassNames.CURSOR); + + method.addStatement("$L cursor = null", com.dbflow5.processor.ClassNames.CURSOR); + method.beginControlFlow("switch($L.match(uri))", ContentProviderDefinition.URI_MATCHER); + for (TableEndpointDefinition tableEndpointDefinition : contentProviderDefinition.endpointDefinitions) { + tableEndpointDefinition.contentUriDefinitions + .stream().filter(it -> it.queryEnabled) + .forEach(definition -> { + method.beginControlFlow("case $L:", definition.name); + method.addCode(getSegmentsPreparation(definition)); + method.addCode("cursor = $T.getDatabase($T.class).query($S, projection, ", + com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, + tableEndpointDefinition.tableName); + method.addCode(getSelectionAndSelectionArgs(definition)); + method.addCode(", null, null, sortOrder);\n"); + method.addStatement("break"); + method.endControlFlow(); + }); + } + method.endControlFlow(); + + method.beginControlFlow("if (cursor != null)"); + method.addStatement("cursor.setNotificationUri(getContext().getContentResolver(), uri)"); + method.endControlFlow(); + method.addStatement("return cursor"); + + return method.build(); + } + } + + /** + * Description: + */ + public static class UpdateMethod implements MethodDefinition { + private final ContentProviderDefinition contentProviderDefinition; + private final ProcessorManager manager; + + public UpdateMethod(ContentProviderDefinition contentProviderDefinition, ProcessorManager manager) { + this.contentProviderDefinition = contentProviderDefinition; + this.manager = manager; + } + + @Override + public MethodSpec methodSpec() { + MethodSpec.Builder method = MethodSpec.methodBuilder("update") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(com.dbflow5.processor.ClassNames.URI, Constants.PARAM_URI) + .addParameter(com.dbflow5.processor.ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) + .addParameter(ClassName.get(String.class), "selection") + .addParameter(ArrayTypeName.of(String.class), "selectionArgs") + .returns(TypeName.INT); + + method.beginControlFlow("switch(MATCHER.match($L))", Constants.PARAM_URI); + for (TableEndpointDefinition tableEndpointDefinition : contentProviderDefinition.endpointDefinitions) { + tableEndpointDefinition.contentUriDefinitions + .stream().filter(it -> it.updateEnabled) + .forEach(definition -> { + method.beginControlFlow("case $L:", definition.name); + method.addStatement("$T adapter = $T.getModelAdapter($T.getTableClassForName($T.class, $S))", + com.dbflow5.processor.ClassNames.MODEL_ADAPTER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, + contentProviderDefinition.databaseTypeName, + tableEndpointDefinition.tableName); + method.addCode(getSegmentsPreparation(definition)); + method.addCode( + "long count = $T.getDatabase($T.class).updateWithOnConflict($S, $L, ", + com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, + tableEndpointDefinition.tableName, + Constants.PARAM_CONTENT_VALUES); + method.addCode(getSelectionAndSelectionArgs(definition)); + method.addCode( + ", $T.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));\n", + com.dbflow5.processor.ClassNames.CONFLICT_ACTION); + + CodeBlock.Builder code = CodeBlock.builder(); + new NotifyMethod(tableEndpointDefinition, definition, + com.dbflow5.contentprovider.annotation.NotifyMethod.UPDATE).addCode(code); + method.addCode(code.build()); + + method.addStatement("return (int) count"); + method.endControlFlow(); + }); + } + + CodeBlock.Builder code = CodeBlock.builder(); + appendDefault(code); + method.addCode(code.build()); + method.endControlFlow(); + + return method.build(); + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProviderDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProviderDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..41330f8f66e809cdbabd9e6815ab67baaa4fc35a --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentProviderDefinition.java @@ -0,0 +1,174 @@ +package com.dbflow5.processor.definition.provider; + +import com.dbflow5.StringUtils; +import com.dbflow5.contentprovider.annotation.ContentProvider; +import com.dbflow5.contentprovider.annotation.TableEndpoint; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.MethodDefinition; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.Validators; +import com.dbflow5.processor.definition.BaseDefinition; +import com.dbflow5.processor.utils.ElementExtensions; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Description: Writes the [ContentProvider] class. + */ +public class ContentProviderDefinition extends BaseDefinition { + + public ContentProviderDefinition(ContentProvider provider, Element typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager, null); + databaseTypeName = ProcessorUtils.extractTypeNameFromAnnotation(provider, e -> null, contentProvider -> { + //contentProvider.database; + return null; + }); + authority = provider.authority(); + holderClass = ProcessorUtils.extractTypeNameFromAnnotation(provider, e -> null, contentProvider -> { + //contentProvider.initializeHolderClass; + return null; + }); + + // init + setOutputClassName("_" + DEFINITION_NAME); + + Validators.TableEndpointValidator validator = new Validators.TableEndpointValidator(); + List elements = manager.elements.getAllMembers((TypeElement)typeElement); + elements.forEach(element -> { + TableEndpoint tableEndpoint = element.getAnnotation(TableEndpoint.class); + TableEndpointDefinition endpointDefinition = new TableEndpointDefinition(tableEndpoint, element, manager); + if (validator.validate(processorManager, endpointDefinition)) { + endpointDefinitions.add(endpointDefinition); + } + }); + + if (!ProcessorUtils.isSubclass(ElementExtensions.toTypeElement(databaseTypeName, manager), manager.processingEnvironment, + ClassNames.CONTENT_PROVIDER_DATABASE)) { + manager.logError("A Content Provider database $elementClassName " + + "must extend ${ClassNames.CONTENT_PROVIDER_DATABASE}"); + } + + if (holderClass != TypeName.OBJECT && + !ProcessorUtils.isSubclass(ElementExtensions.toTypeElement(holderClass, manager), manager.processingEnvironment, ClassNames.DATABASE_HOLDER)) { + manager.logError("The initializeHolderClass $holderClass must point to a subclass" + + "of ${ClassNames.DATABASE_HOLDER}"); + } + } + + public TypeName databaseTypeName; + public List endpointDefinitions = new ArrayList<>(); + + private final String authority; + private final TypeName holderClass; + + private final MethodDefinition[] methods = {new com.dbflow5.processor.definition.provider.ContentProvider.QueryMethod(this, manager), + new com.dbflow5.processor.definition.provider.ContentProvider.InsertMethod(this, false), + new com.dbflow5.processor.definition.provider.ContentProvider.InsertMethod(this, true), + new com.dbflow5.processor.definition.provider.ContentProvider.DeleteMethod(this, manager), + new com.dbflow5.processor.definition.provider.ContentProvider.UpdateMethod(this, manager)}; + + + @Override + public TypeName extendsClass() { + return ClassNames.BASE_CONTENT_PROVIDER; + } + + @Override + public void onWriteDefinition(TypeSpec.Builder typeBuilder) { + if (holderClass != TypeName.OBJECT) { + MethodSpec.Builder builder = MethodSpec.constructorBuilder(); + builder.addStatement("super($T.class)", holderClass); + typeBuilder.addMethod(builder.build()); + } + + AtomicInteger code = new AtomicInteger(); + for (TableEndpointDefinition endpointDefinition : endpointDefinitions) { + endpointDefinition.contentUriDefinitions.forEach(definition -> { + FieldSpec.Builder fieldBuilder = FieldSpec.builder(TypeName.INT, definition.name) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); + fieldBuilder.initializer(CodeBlock.builder().add(String.valueOf(code.get())).build()); + + typeBuilder.addField(fieldBuilder.build()); + code.getAndIncrement(); + }); + } + + FieldSpec.Builder fieldBuilder = FieldSpec.builder(ClassNames.URI_MATCHER, URI_MATCHER) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL); + fieldBuilder.initializer(CodeBlock.builder().add("new $T($T.NO_MATCH)", ClassNames.URI_MATCHER, ClassNames.URI_MATCHER).build()); + typeBuilder.addField(fieldBuilder.build()); + + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate") + .returns(TypeName.BOOLEAN) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(Override.class) + .addStatement("final $T $AUTHORITY = $L", String.class, authority.contains("R.string.")? "getContext().getString("+authority+")" : "\""+authority+"\""); + + for (TableEndpointDefinition endpointDefinition : endpointDefinitions) { + endpointDefinition.contentUriDefinitions.forEach(definition -> { + String path; + if (!StringUtils.isNullOrEmpty(definition.path)) { + path = "\""+definition.path+"\""; + } else { + path = CodeBlock.builder().add("$L.$L.getPath()", definition.elementClassName, definition.name).build().toString(); + } + methodBuilder.addStatement("$L.addURI($L, $L, $L)", URI_MATCHER, AUTHORITY, path, definition.name); + }); + } + + methodBuilder.addStatement("return super.onCreate()"); + typeBuilder.addMethod(methodBuilder.build()); + + + MethodSpec.Builder methodBuilder2 = MethodSpec.methodBuilder("getDatabaseName") + .returns(String.class) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return $T.getDatabaseName($T.class)", ClassNames.FLOW_MANAGER, databaseTypeName); + + typeBuilder.addMethod(methodBuilder2.build()); + + MethodSpec.Builder methodSpec3 = MethodSpec.methodBuilder("getType") + .returns(String.class) + .addParameter(ParameterSpec.builder(ClassNames.URI, "uri").build()) + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + + codeBuilder.addStatement("$T type = null", ClassName.get(String.class)); + codeBuilder.beginControlFlow("switch($L.match(uri))", URI_MATCHER); + + endpointDefinitions.forEach(it -> it.contentUriDefinitions.forEach(definition -> { + codeBuilder.beginControlFlow("case $L:", definition.name); + codeBuilder.addStatement("type = $S", definition.type); + codeBuilder.addStatement("break"); + })); + com.dbflow5.processor.definition.provider.ContentProvider.appendDefault(codeBuilder); + codeBuilder.endControlFlow(); + + codeBuilder.addStatement("return type"); + + methodSpec3.addCode(codeBuilder.build()); + + typeBuilder.addMethod(methodSpec3.build()); + + for(MethodDefinition definition : methods) { + if(definition != null) { + typeBuilder.addMethod(definition.methodSpec()); + } + } + } + + public static final String DEFINITION_NAME = "Provider"; + public static final String URI_MATCHER = "MATCHER"; + private static final String AUTHORITY = "AUTHORITY"; +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentUriDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentUriDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..72a39e1fbb03aa4e54ab14ac8683a03ed4d4f6e0 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/provider/ContentUriDefinition.java @@ -0,0 +1,50 @@ +package com.dbflow5.processor.definition.provider; + +import com.dbflow5.contentprovider.annotation.ContentUri; +import com.dbflow5.contentprovider.annotation.PathSegment; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.BaseDefinition; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + +/** + * Description: + */ +public class ContentUriDefinition extends BaseDefinition { + + public String name; + + public String path; + public String type; + public boolean queryEnabled; + public boolean insertEnabled; + public boolean deleteEnabled; + public boolean updateEnabled; + public PathSegment[] segments; + + public ContentUriDefinition(ContentUri contentUri, Element typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager, processorManager.elements.getPackageOf(typeElement) != null && + processorManager.elements.getPackageOf(typeElement).getQualifiedName() != null? + processorManager.elements.getPackageOf(typeElement).getQualifiedName().toString() : ""); + name = typeElement.getEnclosingElement().getSimpleName() + "_" + typeElement.getSimpleName(); + path = contentUri.path(); + type = contentUri.type(); + queryEnabled = contentUri.queryEnabled(); + insertEnabled = contentUri.insertEnabled(); + deleteEnabled = contentUri.deleteEnabled(); + updateEnabled = contentUri.updateEnabled(); + segments = contentUri.segments(); + + if (typeElement instanceof VariableElement) { + if (ClassNames.URI != elementTypeName) { + processorManager.logError("Content Uri field returned wrong type. It must return a Uri"); + } + } else if (typeElement instanceof ExecutableElement) { + if (ClassNames.URI != elementTypeName) { + processorManager.logError("ContentUri method returns wrong type. It must return Uri"); + } + } + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/definition/provider/NotifyDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/provider/NotifyDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..55cd2115c5241d1a29057424aea7d3519a5339b8 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/provider/NotifyDefinition.java @@ -0,0 +1,86 @@ +package com.dbflow5.processor.definition.provider; + +import com.dbflow5.contentprovider.annotation.Notify; +import com.dbflow5.contentprovider.annotation.NotifyMethod; +import com.dbflow5.processor.ClassNames; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.Adders; +import com.dbflow5.processor.definition.BaseDefinition; +import com.squareup.javapoet.CodeBlock; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * Description: Writes code for [Notify] annotation. + */ +public class NotifyDefinition extends BaseDefinition implements Adders.CodeAdder { + + public NotifyDefinition(Notify notify, ExecutableElement typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager); + paths = notify.paths(); + method = notify.notifyMethod(); + parent = ((TypeElement)typeElement.getEnclosingElement()).getQualifiedName().toString(); + methodName = typeElement.getSimpleName().toString(); + + StringBuilder builder = new StringBuilder(); + for (VariableElement it : typeElement.getParameters()) { + String typeName = it.asType().toString(); + if(typeName.equals("ohos.app.Context")){ + builder.append("getContext()"); + }else if(typeName.equals("ohos.utils.net.Uri")){ + builder.append("uri"); + }else if(typeName.equals("ohos.data.rdb.ValuesBucket")){ + builder.append("values"); + }else if(typeName.equals("long")){ + builder.append("id"); + }else if(typeName.equals("java.lang.String")){ + builder.append("where"); + }else if(typeName.equals("java.lang.String[]")){ + builder.append("whereArgs"); + }else { + builder.append(""); + } + } + params = builder.toString(); + + TypeMirror typeMirror = typeElement.getReturnType(); + if((ClassNames.URI + "[]").equals(typeMirror.toString())) { + returnsArray = true; + }else if(ClassNames.URI.toString().equals(typeMirror.toString())) { + returnsSingle = true; + }else { + processorManager.logError("Notify method returns wrong type. It must return Uri or Uri[]"); + } + } + + public String[] paths; + public NotifyMethod method; + private String parent; + private String methodName; + private String params; + + public boolean returnsArray = false; + public boolean returnsSingle = false; + + @Override + public CodeBlock.Builder addCode(CodeBlock.Builder code) { + if (returnsArray) { + code.addStatement("$T[] notifyUris$L = $L.$L($L)", ClassNames.URI, + methodName, parent, + methodName, params); + code.beginControlFlow("for ($T notifyUri: notifyUris$L)", ClassNames.URI, methodName); + } else { + code.addStatement("$T notifyUri$L = $L.$L($L)", ClassNames.URI, + methodName, parent, + methodName, params); + } + code.addStatement("getContext().getContentResolver().notifyChange(notifyUri$L, null)", + returnsArray? "" : methodName); + if (returnsArray) { + code.endControlFlow(); + } + return code; + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/definition/provider/TableEndpointDefinition.java b/processor/src/main/java/com/dbflow5/processor/definition/provider/TableEndpointDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..419b1e9421d1adb62efcd8da4c522c4aa0a9b1a8 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/definition/provider/TableEndpointDefinition.java @@ -0,0 +1,79 @@ +package com.dbflow5.processor.definition.provider; + +import com.dbflow5.contentprovider.annotation.ContentUri; +import com.dbflow5.contentprovider.annotation.Notify; +import com.dbflow5.contentprovider.annotation.NotifyMethod; +import com.dbflow5.contentprovider.annotation.TableEndpoint; +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.BaseDefinition; +import com.dbflow5.processor.utils.ProcessorUtils; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Description: + */ +public class TableEndpointDefinition extends BaseDefinition { + + public List contentUriDefinitions = new ArrayList<>(); + + /** + * Dont want duplicate paths. + */ + public Map pathValidationMap = new HashMap<>(); + + public Map>> notifyDefinitionPathMap = new HashMap<>(); + + public String tableName = null; + + public TypeName contentProviderName; + + public boolean isTopLevel; + + public TableEndpointDefinition(TableEndpoint tableEndpoint, Element typeElement, ProcessorManager processorManager) { + super(typeElement, processorManager, processorManager.elements.getPackageOf(typeElement) != null && + processorManager.elements.getPackageOf(typeElement).getQualifiedName() != null? + processorManager.elements.getPackageOf(typeElement).getQualifiedName().toString() + : ""); + + contentProviderName = ProcessorUtils.extractTypeNameFromAnnotation(tableEndpoint, e -> null, tableEndpoint1 -> { + tableName = tableEndpoint1.name(); + //tableEndpoint.contentProvider; + return null; + }); + + isTopLevel = typeElement.getEnclosingElement() instanceof PackageElement; + + List elements = processorManager.elements.getAllMembers((TypeElement) typeElement); + for (Element innerElement : elements) { + ContentUri contentUri = innerElement.getAnnotation(ContentUri.class); + if (contentUri != null) { + ContentUriDefinition contentUriDefinition = new ContentUriDefinition(contentUri, innerElement, processorManager); + if (!pathValidationMap.containsKey(contentUriDefinition.path)) { + contentUriDefinitions.add(contentUriDefinition); + } else { + processorManager.logError("There must be unique paths " + + "for the specified @ContentUri ${contentUriDefinition.name} " + + "from $contentProviderName"); + } + } + Notify notify = innerElement.getAnnotation(Notify.class); + if (innerElement instanceof ExecutableElement) { + NotifyDefinition notifyDefinition = new NotifyDefinition(notify, (ExecutableElement) innerElement, processorManager); + for (String path : notifyDefinition.paths) { + Map> methodListMap = notifyDefinitionPathMap.getOrDefault(path, new HashMap<>()); + List notifyDefinitionList = methodListMap.getOrDefault(notifyDefinition.method, new ArrayList<>()); + notifyDefinitionList.add(notifyDefinition); + } + } + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/utils/CodeExtensions.java b/processor/src/main/java/com/dbflow5/processor/utils/CodeExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..0ed408a300cb23cdf9507c49a3c2299fd5642a0f --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/CodeExtensions.java @@ -0,0 +1,65 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.StringUtils; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; + +import java.util.function.Function; + +/** + * Description: Set of utility methods to save code + * + * @author Andrew Grosner (fuzz) + */ +public class CodeExtensions{ + /** + * Collapses the control flow into an easy to use block + */ + public static CodeBlock.Builder controlFlow(CodeBlock.Builder builder, String statement, Function method, Object... args) { + CodeBlock.Builder newBuilder = builder.beginControlFlow(statement, args); + method.apply(newBuilder); + return newBuilder.endControlFlow(); + } + + public static MethodSpec.Builder controlFlow(MethodSpec.Builder builder, String statement, Function method, Object... args) { + MethodSpec.Builder newBuilder = builder.beginControlFlow(statement, args); + CodeBlock.Builder codeBuilder = CodeBlock.builder(); + method.apply(codeBuilder); + newBuilder.addCode(codeBuilder.build()); + + return newBuilder.endControlFlow(); + } + + + /** + * Description: Convenience method for adding [CodeBlock] statements without needing to do so every time. + * + * @author Andrew Grosner (fuzz) + */ + public static CodeBlock.Builder statement(CodeBlock.Builder builder, CodeBlock codeBlock) { + return builder.addStatement("$L", codeBlock); + } + + public static MethodSpec.Builder statement(MethodSpec.Builder builder, CodeBlock codeBlock) { + return builder.addStatement("$L", codeBlock); + } + + public static CodeBlock.Builder _catch(CodeBlock.Builder builder, Class exception, Function function) { + return end(nextControl(builder, "catch", "$T e", function, exception), ""); + } + + private static CodeBlock.Builder nextControl(CodeBlock.Builder builder, String name, String statement, Function function, Object... args) { + return builder.nextControlFlow(name + (StringUtils.isNullOrEmpty(statement)? "" : " (" + statement + ")"), args) + .add(function.apply(CodeBlock.builder()).build()); + } + + private static CodeBlock.Builder end(CodeBlock.Builder builder, String statement, Object... args) { + return !StringUtils.isNullOrEmpty(statement)? builder.endControlFlow(statement, args) : builder.endControlFlow(); + } + + public static CodeBlock codeBlock(Function function) { + return function.apply(CodeBlock.builder()).build(); + } + + +} diff --git a/processor/src/main/java/com/dbflow5/processor/utils/DependencyUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/DependencyUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..dc13bd9c3ed8b763cfa8ecd5252cfddb6d4aea07 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/DependencyUtils.java @@ -0,0 +1,12 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.processor.ProcessorManager; + +public class DependencyUtils{ + /** + * Used to check if class exists on class path, if so, we add the annotation to generated class files. + */ + public static boolean hasJavaX() { + return ProcessorManager.manager.elements.getTypeElement("javax.annotation.Generated") != null; + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/utils/ElementExtensions.java b/processor/src/main/java/com/dbflow5/processor/utils/ElementExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..254941bb29a5abd01960c5b2c0e424e2ec53597b --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/ElementExtensions.java @@ -0,0 +1,109 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.processor.ProcessorManager; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import java.lang.annotation.Annotation; + +/** + * element extensions + */ +public class ElementExtensions { + public static TypeElement toTypeElement(Element element, ProcessorManager manager) { + if (element != null) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return toTypeElement(element.asType(), manager); + } + return null; + } + + public static TypeElement toTypeErasedElement(Element element, ProcessorManager manager) { + if (element != null) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return toTypeElement(erasure(element.asType(), manager), manager); + } + return null; + } + + public static String simpleString(Element element) { + return element.getSimpleName().toString(); + } + + public static TypeElement toTypeElement(TypeMirror typeMirror, ProcessorManager manager) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return manager.elements.getTypeElement(typeMirror.toString()); + } + + public static TypeMirror erasure(TypeMirror typeMirror, ProcessorManager manager) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return manager.typeUtils.erasure(typeMirror); + } + + // TypeName + public static TypeElement toTypeElement(TypeName typeName, ProcessorManager manager) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return manager.elements.getTypeElement(typeName.toString()); + } + + public static T annotation(Class clazz, Element element) { + if (element != null) { + return element.getAnnotation(clazz); + } + return null; + } + + public static PackageElement getPackage(Element element, ProcessorManager manager) { + if (manager == null) { + manager = ProcessorManager.manager; + } + return manager.elements.getPackageOf(element); + } + + public static ClassName toClassName(Element element, ProcessorManager manager) { + if (manager == null) { + manager = ProcessorManager.manager; + } + + if (element == null) { + return null; + } else if (element instanceof TypeElement) { + return ClassName.get((TypeElement) element); + } else { + return ElementUtility.getClassName(element.asType().toString(), manager); + } + } + + public static boolean isOneOf(TypeName typeName, Class... kClass) { + if (typeName != null) { + for (Class clazz : kClass) { + if (com.squareup.javapoet.TypeName.get(clazz) == typeName) { + return true; + } + } + } + return false; + } + + public static TypeName rawTypeName(TypeName typeName) { + if (typeName instanceof ParameterizedTypeName) { + return ((ParameterizedTypeName) typeName).rawType; + } + return typeName; + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/utils/ElementUtility.java b/processor/src/main/java/com/dbflow5/processor/utils/ElementUtility.java new file mode 100644 index 0000000000000000000000000000000000000000..e94d5132f8dc979e7758a018a61d8cddaa60c2d8 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/ElementUtility.java @@ -0,0 +1,77 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.annotation.ColumnIgnore; +import com.dbflow5.processor.ProcessorManager; +import com.squareup.javapoet.ClassName; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import java.util.List; + +/** + * Description: + */ +public class ElementUtility { + + /** + * @return real full-set of elements, including ones from super-class. + */ + public static List getAllElements(TypeElement element, ProcessorManager manager) { + List elements = (List)manager.elements.getAllMembers(element); + TypeMirror superMirror = null; + TypeElement typeElement = element; + if(typeElement != null){ + superMirror = typeElement.getSuperclass(); + while (typeElement.getSuperclass() != null) { + typeElement = (TypeElement)manager.typeUtils.asElement(superMirror); + if(typeElement != null){ + List superElements = (List)manager.elements.getAllMembers(typeElement); + superElements.forEach(it -> { + if (!elements.contains(it)) { + elements.add(it); + } + }); + } + } + } + return elements; + } + + public static boolean isInSamePackage(ProcessorManager manager, Element elementToCheck, Element original) { + return manager.elements.getPackageOf(elementToCheck).toString() == manager.elements.getPackageOf(original).toString(); + } + + public static boolean isPackagePrivate(Element element) { + return !element.getModifiers().contains(Modifier.PUBLIC) && !element.getModifiers().contains(Modifier.PRIVATE) + && !element.getModifiers().contains(Modifier.STATIC); + } + + public static boolean isValidAllFields(boolean allFields, Element element) { + return allFields && element.getKind().isField() && + !element.getModifiers().contains(Modifier.STATIC) && + !element.getModifiers().contains(Modifier.FINAL) && + ElementExtensions.annotation(ColumnIgnore.class, element) == null; + } + + /** + * Attempts to retrieve a [ClassName] from the [elementClassname] Fully-qualified name. If it + * does not exist yet via [ClassName.get], we manually create the [ClassName] object to reference + * later at compile time validation. + */ + public static ClassName getClassName(String elementClassname, ProcessorManager manager) { + TypeElement typeElement = manager.elements.getTypeElement(elementClassname); + if (typeElement != null) { + return ClassName.get(typeElement); + } else { + String[] names = elementClassname.split("."); + if (names.length > 0) { + // attempt to take last part as class name + String className = names[names.length - 1]; + return ClassName.get(elementClassname.replace("." + className, ""), className); + } else { + return null; + } + } + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/utils/JavaPoetExtensions.java b/processor/src/main/java/com/dbflow5/processor/utils/JavaPoetExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..b1490df900b65c17173c97281068ef099e2d7c7c --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/JavaPoetExtensions.java @@ -0,0 +1,73 @@ +package com.dbflow5.processor.utils; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class JavaPoetExtensions{ + public static TypeSpec.Builder overrideFun(TypeSpec.Builder builder, TypeName type, String name, Function codeMethod, ParameterSpec.Builder... params) { + List list = new ArrayList<>(); + for(ParameterSpec.Builder b : params) { + list.add(b.build()); + } + + MethodSpec.Builder mBuilder = MethodSpec.methodBuilder(name) + .returns(type) + .addParameters(list) + .addAnnotation(Override.class); + codeMethod.apply(mBuilder); + + return builder.addMethod(mBuilder.build()); + } + + public static TypeSpec.Builder overrideFun(TypeSpec.Builder builder, Class type, String name, Function codeMethod, ParameterSpec.Builder... params) { + List list = new ArrayList<>(); + for(ParameterSpec.Builder b : params) { + list.add(b.build()); + } + + MethodSpec.Builder mBuilder = MethodSpec + .methodBuilder(name) + .returns(type) + .addParameters(list) + .addAnnotation(Override.class); + codeMethod.apply(mBuilder); + return builder.addMethod(mBuilder.build()); + } + + public static MethodSpec overrideFun(TypeName type, String name, Function codeMethod, ParameterSpec.Builder... params) { + List list = new ArrayList<>(); + for(ParameterSpec.Builder b : params) { + list.add(b.build()); + } + + MethodSpec.Builder mBuilder = MethodSpec + .methodBuilder(name) + .returns(type) + .addParameters(list) + .addAnnotation(Override.class); + codeMethod.apply(mBuilder); + return mBuilder.build(); + } + + public static MethodSpec overrideFun(Class type, String name, Function codeMethod, ParameterSpec.Builder... params) { + List list = new ArrayList<>(); + for(ParameterSpec.Builder b : params) { + list.add(b.build()); + } + + MethodSpec.Builder mBuilder = MethodSpec + .methodBuilder(name) + .returns(type) + .addParameters(list) + .addAnnotation(Override.class); + codeMethod.apply(mBuilder); + return mBuilder.build(); + } + +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/utils/LetUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/LetUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..7ef72c83bdd8b7b1c6e6d745823d618c4f64df43 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/LetUtils.java @@ -0,0 +1,12 @@ +package com.dbflow5.processor.utils; + +import java.util.function.BiFunction; + +/** + * Description: Multi-let execution. + */ +public class LetUtils{ + public static void safeLet(A a, B b, BiFunction fn) { + if (a != null && b != null) fn.apply(a, b); + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/utils/ModelUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/ModelUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..1382bbda49dfbcb25f73bcb30837341e2fb69b91 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/ModelUtils.java @@ -0,0 +1,8 @@ +package com.dbflow5.processor.utils; + +public class ModelUtils { + + public static final String variable = "model"; + + public static final String wrapper = "wrapper"; +} diff --git a/processor/src/main/java/com/dbflow5/processor/utils/ProcessorUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/ProcessorUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5db16f5d69e6bbdb682bdfa714f5ffe08e32c083 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/ProcessorUtils.java @@ -0,0 +1,140 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.processor.ProcessorManager; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.function.Function; + +/** + * Whether the specified element implements the [ClassName] + */ +public class ProcessorUtils{ + public static boolean implementsClass(TypeElement element, ProcessingEnvironment processingEnvironment, ClassName className) { + return implementsClass(element, processingEnvironment, className.toString()); + } + + /** + * Whether the specified element is assignable to the fqTn parameter + + * @param processingEnvironment The environment this runs in + * * + * @param fqTn THe fully qualified type name of the element we want to check + * * + * @param element The element to check that implements + * * + * @return true if element implements the fqTn + */ + public static boolean implementsClass(TypeElement element, ProcessingEnvironment processingEnvironment, String fqTn) { + TypeElement typeElement = processingEnvironment.getElementUtils().getTypeElement(fqTn); + if (typeElement == null) { + processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Type Element was null for: "+fqTn+" ensure that the visibility of the class is not private."); + return false; + } else { + TypeMirror classMirror = ElementExtensions.erasure(typeElement.asType(), ProcessorManager.manager); + if (classMirror == null || element.asType() == null) { + return false; + } + TypeMirror elementType = element.asType(); + return elementType != null && (processingEnvironment.getTypeUtils().isAssignable(elementType, classMirror) || elementType == classMirror); + } + } + + /** + * Whether the specified element is assignable to the [className] parameter + */ + public static boolean isSubclass(TypeElement element, ProcessingEnvironment processingEnvironment, ClassName className) { + return isSubclass(element, processingEnvironment, className.toString()); + } + + /** + * Whether the specified element is assignable to the [fqTn] parameter + */ + public static boolean isSubclass(TypeElement element, ProcessingEnvironment processingEnvironment, String fqTn) { + TypeElement typeElement = processingEnvironment.getElementUtils().getTypeElement(fqTn); + if (typeElement == null) { + processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Type Element was null for: "+fqTn+" ensure that the visibility of the class is not private."); + return false; + } else { + TypeMirror classMirror = typeElement.asType(); + return classMirror != null && element != null && element.asType() != null && processingEnvironment.getTypeUtils().isSubtype(element.asType(), classMirror); + } + } + + public static ClassName fromTypeMirror(TypeMirror typeMirror, ProcessorManager processorManager) { + TypeElement element = getTypeElement(typeMirror); + if (element != null) { + return ClassName.get(element); + } else { + return ElementUtility.getClassName(typeMirror.toString(), processorManager); + } + } + + public static TypeElement getTypeElement(Element element) { + if(element instanceof TypeElement) { + return (TypeElement)element; + }else { + return getTypeElement(element.asType()); + } + } + + public static TypeElement getTypeElement(TypeMirror typeMirror) { + ProcessorManager manager = ProcessorManager.manager; + TypeElement typeElement = ElementExtensions.toTypeElement(typeMirror, manager); + if (typeElement == null) { + Element el = manager.typeUtils.asElement(typeMirror); + if (el instanceof TypeElement) { + typeElement = (TypeElement)el; + }else { + typeElement = null; + } + } + return typeElement; + } + + public static void ensureVisibleStatic(Element element, TypeElement typeElement, String name) { + if (element.getModifiers().contains(Modifier.PRIVATE) || element.getModifiers().contains(Modifier.PROTECTED)) { + ProcessorManager.manager.logError("$name must be visible from: " + typeElement); + } + if (!element.getModifiers().contains(Modifier.STATIC)) { + ProcessorManager.manager.logError("$name must be static from: " + typeElement); + } + + if (!element.getModifiers().contains(Modifier.FINAL)) { + ProcessorManager.manager.logError("The $name must be final"); + } + } + + public static TypeName extractTypeNameFromAnnotation(Element element, Class clazz, Function invoker) { + A a = ElementExtensions.annotation(clazz, element); + try { + invoker.apply(a); + } catch (MirroredTypeException mte) { + return TypeName.get(mte.getTypeMirror()); + } + return null; + } + + public static TypeMirror extractTypeMirrorFromAnnotation(A a, Function exceptionHandler, Function invoker) { + TypeMirror mirror = null; + try { + invoker.apply(a); + } catch (MirroredTypeException mte) { + exceptionHandler.apply(mte); + mirror = mte.getTypeMirror(); + } + return mirror; + } + + public static TypeName extractTypeNameFromAnnotation(A a, Function exceptionHandler, Function invoker) { + return TypeName.get(extractTypeMirrorFromAnnotation(a, exceptionHandler, invoker)); + } +} \ No newline at end of file diff --git a/processor/src/main/java/com/dbflow5/processor/utils/StringUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..7126e505ea71bc23b4a95bd5c56011d8f2c4f57a --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/StringUtils.java @@ -0,0 +1,26 @@ +package com.dbflow5.processor.utils; + +/** + * Description: + */ +public class StringUtils{ + public static boolean isNullOrEmpty(String str) { + return str == null || str.trim().isEmpty() || str.equals("null"); + } + + public static String capitalizeFirstLetter(String str) { + if (str == null || str.trim().isEmpty()) { + return str != null? str : ""; + } + + return com.dbflow5.StringUtils.capitalize(str); + } + + public static String lower(String str) { + if (str == null || str.trim().isEmpty()) { + return str != null? str : ""; + } + + return str.substring(0, 1).toLowerCase() + str.substring(1); + } +} diff --git a/processor/src/main/java/com/dbflow5/processor/utils/WriterUtils.java b/processor/src/main/java/com/dbflow5/processor/utils/WriterUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c60fcc39cc0be432cd756124cdf4f6da88e10f67 --- /dev/null +++ b/processor/src/main/java/com/dbflow5/processor/utils/WriterUtils.java @@ -0,0 +1,33 @@ +package com.dbflow5.processor.utils; + +import com.dbflow5.processor.ProcessorManager; +import com.dbflow5.processor.definition.BaseDefinition; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +import java.util.function.Function; + +public class WriterUtils{ + public static boolean writeBaseDefinition(BaseDefinition baseDefinition, ProcessorManager processorManager) { + boolean success = false; + try { + javaFile(baseDefinition.packageName, builder -> builder, unused -> baseDefinition.typeSpec()) + .writeTo(processorManager.processingEnvironment.getFiler()); + success = true; + } catch (IOException e) { + // ignored + } catch (IllegalStateException i) { + processorManager.logError(baseDefinition.getClass(), "Found error for class: $elementName"); + processorManager.logError(baseDefinition.getClass(), i.getMessage()); + } + + return success; + } + + private static JavaFile javaFile(String packageName, Function imports, Function function) { + JavaFile.Builder builder = JavaFile.builder(packageName, function.apply(null)); + builder = imports.apply(builder); + return builder.build(); + } +} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/ClassNames.kt b/processor/src/main/kotlin/com/dbflow5/processor/ClassNames.kt deleted file mode 100644 index 659db1eec3528761a143c4568545d6ddffb05401..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/ClassNames.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.annotation.ConflictAction -import com.squareup.javapoet.ClassName - -/** - * Description: The static FQCN string file to assist in providing class names for imports and more in the Compiler - */ -object ClassNames { - - - val BASE_PACKAGE = "com.dbflow5" - val FLOW_MANAGER_PACKAGE = "$BASE_PACKAGE.config" - val DATABASE_HOLDER_STATIC_CLASS_NAME = "GeneratedDatabaseHolder" - val CONVERTER = "$BASE_PACKAGE.converter" - val ADAPTER = "$BASE_PACKAGE.adapter" - val QUERY_PACKAGE = "$BASE_PACKAGE.query" - val STRUCTURE = "$BASE_PACKAGE.structure" - val DATABASE = "$BASE_PACKAGE.database" - val QUERIABLE = "$ADAPTER.queriable" - val PROPERTY_PACKAGE = "$QUERY_PACKAGE.property" - val CONFIG = "$BASE_PACKAGE.config" - val RUNTIME = "$BASE_PACKAGE.runtime" - val SAVEABLE = "$ADAPTER.saveable" - val PROVIDER = "$BASE_PACKAGE.provider" - - val DATABASE_HOLDER: ClassName = ClassName.get(CONFIG, "DatabaseHolder") - val FLOW_MANAGER: ClassName = ClassName.get(CONFIG, "FlowManager") - val BASE_DATABASE_DEFINITION_CLASSNAME: ClassName = ClassName.get(CONFIG, "DBFlowDatabase") - val CONTENT_PROVIDER_DATABASE: ClassName = ClassName.get(PROVIDER, "ContentProviderDatabase") - - val URI: ClassName = ClassName.get("android.net", "Uri") - val URI_MATCHER: ClassName = ClassName.get("android.content", "UriMatcher") - val CURSOR: ClassName = ClassName.get("android.database", "Cursor") - val FLOW_CURSOR: ClassName = ClassName.get(DATABASE, "FlowCursor") - val DATABASE_UTILS: ClassName = ClassName.get("android.database", "DatabaseUtils") - val CONTENT_VALUES: ClassName = ClassName.get("android.content", "ContentValues") - val CONTENT_URIS: ClassName = ClassName.get("android.content", "ContentUris") - - val MODEL_ADAPTER: ClassName = ClassName.get(ADAPTER, "ModelAdapter") - val RETRIEVAL_ADAPTER: ClassName = ClassName.get(ADAPTER, "RetrievalAdapter") - val MODEL: ClassName = ClassName.get(STRUCTURE, "Model") - val MODEL_VIEW_ADAPTER: ClassName = ClassName.get(ADAPTER, "ModelViewAdapter") - val OBJECT_TYPE: ClassName = ClassName.get(ADAPTER, "ObjectType") - - val DATABASE_STATEMENT: ClassName = ClassName.get(DATABASE, "DatabaseStatement") - - val QUERY: ClassName = ClassName.get(QUERY_PACKAGE, "Query") - - val TYPE_CONVERTER: ClassName = ClassName.get(CONVERTER, "TypeConverter") - val TYPE_CONVERTER_GETTER: ClassName = ClassName.get(PROPERTY_PACKAGE, - "TypeConvertedProperty.TypeConverterGetter") - - val CONFLICT_ACTION: ClassName = ClassName.get(ConflictAction::class.java) - - val CONTENT_VALUES_LISTENER: ClassName = ClassName.get(QUERY_PACKAGE, "ContentValuesListener") - val LOAD_FROM_CURSOR_LISTENER: ClassName = ClassName.get(QUERY_PACKAGE, "LoadFromCursorListener") - val SQLITE_STATEMENT_LISTENER: ClassName = ClassName.get(QUERY_PACKAGE, "SQLiteStatementListener") - - - val PROPERTY: ClassName = ClassName.get(PROPERTY_PACKAGE, "Property") - val TYPE_CONVERTED_PROPERTY: ClassName = ClassName.get(PROPERTY_PACKAGE, "TypeConvertedProperty") - val WRAPPER_PROPERTY: ClassName = ClassName.get(PROPERTY_PACKAGE, "WrapperProperty") - - val IPROPERTY: ClassName = ClassName.get(PROPERTY_PACKAGE, "IProperty") - val INDEX_PROPERTY: ClassName = ClassName.get(PROPERTY_PACKAGE, "IndexProperty") - val OPERATOR_GROUP: ClassName = ClassName.get(QUERY_PACKAGE, "OperatorGroup") - - val ICONDITIONAL: ClassName = ClassName.get(QUERY_PACKAGE, "IConditional") - - val BASE_CONTENT_PROVIDER: ClassName = ClassName.get(PROVIDER, "BaseContentProvider") - - val BASE_MODEL: ClassName = ClassName.get(STRUCTURE, "BaseModel") - val MODEL_CACHE: ClassName = ClassName.get("$QUERY_PACKAGE.cache", "ModelCache") - val MULTI_KEY_CACHE_CONVERTER: ClassName = ClassName.get("$QUERY_PACKAGE.cache", "MultiKeyCacheConverter") - val SIMPLE_MAP_CACHE: ClassName = ClassName.get("$QUERY_PACKAGE.cache", "SimpleMapCache") - - val CACHEABLE_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "CacheableModelLoader") - val SINGLE_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "SingleModelLoader") - val CACHEABLE_LIST_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "CacheableListModelLoader") - val LIST_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "ListModelLoader") - val CACHE_ADAPTER: ClassName = ClassName.get(ADAPTER, "CacheAdapter") - - val DATABASE_WRAPPER: ClassName = ClassName.get(DATABASE, "DatabaseWrapper") - - val SQLITE: ClassName = ClassName.get(QUERY_PACKAGE, "SQLite") - - val CACHEABLE_LIST_MODEL_SAVER: ClassName = ClassName.get(SAVEABLE, "CacheableListModelSaver") - val SINGLE_MODEL_SAVER: ClassName = ClassName.get(SAVEABLE, "ModelSaver") - - val SINGLE_KEY_CACHEABLE_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "SingleKeyCacheableModelLoader") - val SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER: ClassName = ClassName.get(QUERIABLE, "SingleKeyCacheableListModelLoader") - - val NON_NULL: ClassName = ClassName.get("android.support.annotation", "NonNull") - val NON_NULL_X: ClassName = ClassName.get("androidx.annotation", "NonNull") - - val GENERATED: ClassName = ClassName.get("javax.annotation", "Generated") - - val STRING_UTILS: ClassName = ClassName.get(BASE_PACKAGE, "StringUtils") -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/DBFlowProcessor.kt b/processor/src/main/kotlin/com/dbflow5/processor/DBFlowProcessor.kt deleted file mode 100644 index d5d9ade07d98cbfe0814485f1a922e38ccfda6fe..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/DBFlowProcessor.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnIgnore -import com.dbflow5.annotation.Fts3 -import com.dbflow5.annotation.Fts4 -import com.dbflow5.annotation.Migration -import com.dbflow5.annotation.ModelView -import com.dbflow5.annotation.MultipleManyToMany -import com.dbflow5.annotation.QueryModel -import com.dbflow5.annotation.Table -import com.dbflow5.annotation.TypeConverter -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.contentprovider.annotation.TableEndpoint -import com.dbflow5.processor.definition.DatabaseHolderDefinition -import javax.annotation.processing.AbstractProcessor -import javax.annotation.processing.ProcessingEnvironment -import javax.annotation.processing.RoundEnvironment -import javax.lang.model.SourceVersion -import javax.lang.model.element.TypeElement - -class DBFlowProcessor : AbstractProcessor() { - - private lateinit var manager: ProcessorManager - - /** - * If the processor class is annotated with [ ], return an unmodifiable set with the - * same set of strings as the annotation. If the class is not so - * annotated, an empty set is returned. - - * @return the names of the annotation types supported by this - * * processor, or an empty set if none - */ - override fun getSupportedAnnotationTypes() = listOf( - Table::class, - Column::class, - TypeConverter::class, - ModelView::class, - Migration::class, - ContentProvider::class, - TableEndpoint::class, - ColumnIgnore::class, - QueryModel::class, - Fts3::class, - Fts4::class, - MultipleManyToMany::class).mapTo(linkedSetOf()) { it.java.canonicalName } - - override fun getSupportedOptions() = linkedSetOf(DatabaseHolderDefinition.OPTION_TARGET_MODULE_NAME) - - /** - * If the processor class is annotated with [ ], return the source version in the - * annotation. If the class is not so annotated, [ ][javax.lang.model.SourceVersion.RELEASE_6] is returned. - - * @return the latest source version supported by this processor - */ - override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() - - @Synchronized - override fun init(processingEnv: ProcessingEnvironment) { - super.init(processingEnv) - manager = ProcessorManager(processingEnv) - manager.addHandlers(MigrationHandler(), - TypeConverterHandler(), - DatabaseHandler(), - TableHandler(), - QueryModelHandler(), - ModelViewHandler(), - ContentProviderHandler(), - TableEndpointHandler()) - } - - override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { - manager.handle(manager, roundEnv) - - // return true if we successfully processed the Annotation. - return true - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/Handlers.kt b/processor/src/main/kotlin/com/dbflow5/processor/Handlers.kt deleted file mode 100644 index 4e8091a170fd2164216b543e50272dc89e4f0b8b..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/Handlers.kt +++ /dev/null @@ -1,274 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.annotation.Database -import com.dbflow5.annotation.ManyToMany -import com.dbflow5.annotation.Migration -import com.dbflow5.annotation.ModelView -import com.dbflow5.annotation.MultipleManyToMany -import com.dbflow5.annotation.QueryModel -import com.dbflow5.annotation.Table -import com.dbflow5.annotation.TypeConverter -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.contentprovider.annotation.TableEndpoint -import com.dbflow5.converter.BigDecimalConverter -import com.dbflow5.converter.BigIntegerConverter -import com.dbflow5.converter.BooleanConverter -import com.dbflow5.converter.CalendarConverter -import com.dbflow5.converter.CharConverter -import com.dbflow5.converter.DateConverter -import com.dbflow5.converter.SqlDateConverter -import com.dbflow5.converter.UUIDConverter -import com.dbflow5.processor.definition.DatabaseDefinition -import com.dbflow5.processor.definition.ManyToManyDefinition -import com.dbflow5.processor.definition.MigrationDefinition -import com.dbflow5.processor.definition.ModelViewDefinition -import com.dbflow5.processor.definition.QueryModelDefinition -import com.dbflow5.processor.definition.TableDefinition -import com.dbflow5.processor.definition.TypeConverterDefinition -import com.dbflow5.processor.definition.provider.ContentProviderDefinition -import com.dbflow5.processor.definition.provider.TableEndpointDefinition -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.fromTypeMirror -import javax.annotation.processing.RoundEnvironment -import javax.lang.model.element.Element -import javax.lang.model.element.PackageElement -import javax.lang.model.element.TypeElement -import kotlin.reflect.KClass - -/** - * Description: The main base-level handler for performing some action when the - * [DBFlowProcessor.process] is called. - */ -interface Handler { - - /** - * Called when the process of the [DBFlowProcessor] is called - - * @param processorManager The manager that holds processing information - * * - * @param roundEnvironment The round environment - */ - fun handle(processorManager: ProcessorManager, roundEnvironment: RoundEnvironment) -} - -/** - * Description: The base handler than provides common callbacks into processing annotated top-level elements - */ -abstract class AnnotatedHandler(private val annotationClass: KClass) : Handler { - - override fun handle(processorManager: ProcessorManager, roundEnvironment: RoundEnvironment) { - val annotatedElements = roundEnvironment.getElementsAnnotatedWith(annotationClass.java).toMutableSet() - processElements(processorManager, annotatedElements) - if (annotatedElements.size > 0) { - annotatedElements.forEach { element -> - element.getAnnotation(annotationClass.java)?.let { annotation -> - onProcessElement(annotation, element, processorManager) - } - } - afterProcessElements(processorManager) - } - } - - open fun processElements(processorManager: ProcessorManager, annotatedElements: MutableSet) { - - } - - protected abstract fun onProcessElement(annotation: AnnotationClass, element: Element, processorManager: ProcessorManager) - - open fun afterProcessElements(processorManager: ProcessorManager) { - - } -} - -/** - * Description: Handles [Migration] by creating [MigrationDefinition] - * and adds them to the [ProcessorManager] - */ -class MigrationHandler : AnnotatedHandler(Migration::class) { - - override fun onProcessElement(annotation: Migration, element: Element, processorManager: ProcessorManager) { - if (element is TypeElement) { - element.annotation()?.let { migration -> - val migrationDefinition = MigrationDefinition(migration, processorManager, element) - processorManager.addMigrationDefinition(migrationDefinition) - } - } - } -} - -/** - * Description: Handles [ModelView] annotations, writing - * ModelViewAdapters, and adding them to the [ProcessorManager] - */ -class ModelViewHandler : AnnotatedHandler(ModelView::class) { - - override fun onProcessElement(annotation: ModelView, element: Element, processorManager: ProcessorManager) { - if (element is TypeElement) { - element.annotation()?.let { modelView -> - val modelViewDefinition = ModelViewDefinition(modelView, processorManager, element) - processorManager.addModelViewDefinition(modelViewDefinition) - } - } - } -} - -/** - * Description: Handles [QueryModel] annotations, writing QueryModelAdapter, and - * adding them to the [ProcessorManager]. - */ -class QueryModelHandler : AnnotatedHandler(QueryModel::class) { - - override fun onProcessElement(annotation: QueryModel, element: Element, processorManager: ProcessorManager) { - if (element is TypeElement) { - element.annotation()?.let { queryModel -> - val queryModelDefinition = QueryModelDefinition(queryModel, element, processorManager) - processorManager.addQueryModelDefinition(queryModelDefinition) - } - } - } -} - -class TableEndpointHandler : AnnotatedHandler(TableEndpoint::class) { - - private val validator: TableEndpointValidator = TableEndpointValidator() - - override fun onProcessElement(annotation: TableEndpoint, element: Element, processorManager: ProcessorManager) { - - // top-level only - if (element.enclosingElement is PackageElement) { - val tableEndpointDefinition = TableEndpointDefinition(annotation, element, processorManager) - if (validator.validate(processorManager, tableEndpointDefinition)) { - processorManager.putTableEndpointForProvider(tableEndpointDefinition) - } - } - } -} - -/** - * Description: Handles [Table] annotations, writing ModelAdapters, - * and adding them to the [ProcessorManager] - */ -class TableHandler : AnnotatedHandler
(Table::class) { - - override fun onProcessElement(annotation: Table, element: Element, processorManager: ProcessorManager) { - if (element is TypeElement) { - val tableDefinition = TableDefinition(annotation, processorManager, element) - processorManager.addTableDefinition(tableDefinition) - - element.annotation()?.let { manyToMany -> - val manyToManyDefinition = ManyToManyDefinition(element, processorManager, manyToMany) - processorManager.addManyToManyDefinition(manyToManyDefinition) - } - - if (element.annotation() != null) { - val multipleManyToMany = element.annotation() - multipleManyToMany?.value?.forEach { - processorManager.addManyToManyDefinition(ManyToManyDefinition(element, processorManager, it)) - } - } - } - } -} - -/** - * Description: Handles [TypeConverter] annotations, - * adding default methods and adding them to the [ProcessorManager] - */ -class TypeConverterHandler : AnnotatedHandler(TypeConverter::class) { - - private var typeConverterElements = setOf() - private val typeConverterDefinitions = mutableSetOf() - override fun processElements(processorManager: ProcessorManager, annotatedElements: MutableSet) { - typeConverterElements = DEFAULT_TYPE_CONVERTERS.mapTo(mutableSetOf()) { processorManager.elements.getTypeElement(it.name) } - annotatedElements.addAll(typeConverterElements) - } - - override fun onProcessElement(annotation: TypeConverter, element: Element, processorManager: ProcessorManager) { - if (element is TypeElement) { - fromTypeMirror(element.asType(), processorManager)?.let { className -> - val definition = TypeConverterDefinition(annotation, className, element.asType(), processorManager, - isDefaultConverter = typeConverterElements.contains(element)) - if (VALIDATOR.validate(processorManager, definition)) { - // allow user overrides from default. - // Check here if user already placed definition of same type, since default converters - // are added last. - if (processorManager.typeConverters - .filter { it.value.modelTypeName == definition.modelTypeName } - .isEmpty()) { - typeConverterDefinitions.add(definition) - } - } - } - } - } - - override fun afterProcessElements(processorManager: ProcessorManager) { - // validate multiple global registered do not exist. - val grouping = typeConverterDefinitions - .filter { it.isDefaultConverter } - .groupingBy { it.modelTypeName } - val groupingMap = grouping.aggregate { key, accumulator: MutableSet?, element: TypeConverterDefinition, first: Boolean -> - val set = accumulator ?: mutableSetOf() - set.add(element) - return@aggregate set - } - grouping.eachCount() - .forEach { (type, count) -> - if (count > 1) { - processorManager.logError(TypeConverterHandler::class, "Multiple registered @TypeConverter of type $type found. " + - "Pick one for global and make the other a local converter for a @Column. " + - "Or explicitly specify both as field converters." + - "\n${ - groupingMap[type]?.joinToString("\n") { " ${it.className} registered for ${it.modelTypeName} <-> ${it.dbTypeName}" } - }\n") - } - } - // sort default converters first so that they can get overwritten - typeConverterDefinitions - .sortedBy { !it.isDefaultConverter } - .forEach { def -> processorManager.addTypeConverterDefinition(def) } - } - - companion object { - private val VALIDATOR = TypeConverterValidator() - private val DEFAULT_TYPE_CONVERTERS = arrayOf>(CalendarConverter::class.java, - BigDecimalConverter::class.java, BigIntegerConverter::class.java, - DateConverter::class.java, SqlDateConverter::class.java, - BooleanConverter::class.java, UUIDConverter::class.java, - CharConverter::class.java) - } -} - -class ContentProviderHandler : AnnotatedHandler(ContentProvider::class) { - - override fun onProcessElement(annotation: ContentProvider, element: Element, processorManager: ProcessorManager) { - val contentProviderDefinition = ContentProviderDefinition(annotation, element, processorManager) - if (contentProviderDefinition.elementClassName != null) { - processorManager.addContentProviderDefinition(contentProviderDefinition) - } - } -} - -/** - * Description: Deals with writing database definitions - */ -class DatabaseHandler : AnnotatedHandler(Database::class) { - - private val validator = DatabaseValidator() - - override fun onProcessElement(annotation: Database, element: Element, processorManager: ProcessorManager) { - val managerWriter = DatabaseDefinition(annotation, processorManager, element) - if (validator.validate(processorManager, managerWriter)) { - processorManager.addDatabaseDefinition(managerWriter) - } - } - - companion object { - val TYPE_CONVERTER_MAP_FIELD_NAME = "typeConverters" - val MODEL_ADAPTER_MAP_FIELD_NAME = "modelAdapters" - val QUERY_MODEL_ADAPTER_MAP_FIELD_NAME = "queryModelAdapterMap" - val MIGRATION_FIELD_NAME = "migrationMap" - val MODEL_VIEW_ADAPTER_MAP_FIELD_NAME = "modelViewAdapterMap" - val MODEL_NAME_MAP = "modelTableNames" - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/ProcessorManager.kt b/processor/src/main/kotlin/com/dbflow5/processor/ProcessorManager.kt deleted file mode 100644 index 2e4c0af2ec00700e72922c68de4aee85921ab237..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/ProcessorManager.kt +++ /dev/null @@ -1,336 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.processor.definition.DatabaseDefinition -import com.dbflow5.processor.definition.DatabaseHolderDefinition -import com.dbflow5.processor.definition.DatabaseObjectHolder -import com.dbflow5.processor.definition.EntityDefinition -import com.dbflow5.processor.definition.ManyToManyDefinition -import com.dbflow5.processor.definition.MigrationDefinition -import com.dbflow5.processor.definition.ModelViewDefinition -import com.dbflow5.processor.definition.QueryModelDefinition -import com.dbflow5.processor.definition.TableDefinition -import com.dbflow5.processor.definition.TypeConverterDefinition -import com.dbflow5.processor.definition.provider.ContentProviderDefinition -import com.dbflow5.processor.definition.provider.TableEndpointDefinition -import com.dbflow5.processor.definition.safeWritePackageHelper -import com.dbflow5.processor.utils.writeBaseDefinition -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.TypeName -import java.io.IOException -import javax.annotation.processing.FilerException -import javax.annotation.processing.Messager -import javax.annotation.processing.ProcessingEnvironment -import javax.annotation.processing.RoundEnvironment -import javax.lang.model.element.Element -import javax.lang.model.util.Elements -import javax.lang.model.util.Types -import javax.tools.Diagnostic -import kotlin.reflect.KClass - -/** - * Description: The main object graph during processing. This class collects all of the - * processor classes and writes them to the corresponding database holders. - */ -class ProcessorManager internal constructor(val processingEnvironment: ProcessingEnvironment) : Handler { - - companion object { - lateinit var manager: ProcessorManager - } - - private val uniqueDatabases = arrayListOf() - private val modelToDatabaseMap = hashMapOf() - val typeConverters = linkedMapOf() - private val migrations = hashMapOf>>() - - private val databaseDefinitionMap = hashMapOf() - private val handlers = mutableSetOf>() - private val providerMap = hashMapOf() - - init { - manager = this - } - - fun addHandlers(vararg containerHandlers: AnnotatedHandler<*>) { - containerHandlers.forEach { handlers.add(it) } - } - - val messager: Messager = processingEnvironment.messager - - val typeUtils: Types = processingEnvironment.typeUtils - - val elements: Elements = processingEnvironment.elementUtils - - fun addDatabase(database: TypeName) { - if (!uniqueDatabases.contains(database)) { - uniqueDatabases.add(database) - } - } - - fun addDatabaseDefinition(databaseDefinition: DatabaseDefinition) { - val holderDefinition = getOrPutDatabase(databaseDefinition.elementClassName) - holderDefinition?.databaseDefinition = databaseDefinition - } - - fun getDatabaseHolderDefinitionList() = databaseDefinitionMap.values.toList() - - fun getDatabaseHolderDefinition(databaseName: TypeName?) = databaseDefinitionMap[databaseName] - - fun addTypeConverterDefinition(definition: TypeConverterDefinition) { - typeConverters[definition.modelTypeName] = definition - } - - fun getTypeConverterDefinition(typeName: TypeName?): TypeConverterDefinition? = typeConverters[typeName] - - fun addModelToDatabase(modelType: TypeName?, databaseName: TypeName) { - modelType?.let { type -> - addDatabase(databaseName) - modelToDatabaseMap[type] = databaseName - } - } - - fun addQueryModelDefinition(queryModelDefinition: QueryModelDefinition) { - queryModelDefinition.elementClassName?.let { - getOrPutDatabase(queryModelDefinition.associationalBehavior.databaseTypeName) - ?.queryModelDefinitionMap?.put(it, queryModelDefinition) - } - } - - fun addTableDefinition(tableDefinition: TableDefinition) { - tableDefinition.elementClassName?.let { - val holderDefinition = getOrPutDatabase(tableDefinition.associationalBehavior.databaseTypeName) - holderDefinition?.tableDefinitionMap?.put(it, tableDefinition) - holderDefinition?.tableNameMap?.let { - val tableName = tableDefinition.associationalBehavior.name - if (holderDefinition.tableNameMap.containsKey(tableName)) { - logError("Found duplicate table $tableName " + - "for database ${holderDefinition.databaseDefinition?.elementName}") - } else { - holderDefinition.tableNameMap.put(tableName, tableDefinition) - } - } - } - } - - fun addManyToManyDefinition(manyToManyDefinition: ManyToManyDefinition) { - val databaseHolderDefinition = getOrPutDatabase(manyToManyDefinition.databaseTypeName) - databaseHolderDefinition?.manyToManyDefinitionMap?.let { - manyToManyDefinition.elementClassName?.let { elementClassName -> - it.getOrPut(elementClassName) { arrayListOf() } - .add(manyToManyDefinition) - } - } - } - - fun getTableDefinition(databaseName: TypeName?, typeName: TypeName?): TableDefinition? { - return getOrPutDatabase(databaseName)?.tableDefinitionMap?.get(typeName) - } - - fun getQueryModelDefinition(databaseName: TypeName?, typeName: TypeName?): QueryModelDefinition? { - return getOrPutDatabase(databaseName)?.queryModelDefinitionMap?.get(typeName) - } - - fun getModelViewDefinition(databaseName: TypeName?, typeName: TypeName?): ModelViewDefinition? { - return getOrPutDatabase(databaseName)?.modelViewDefinitionMap?.get(typeName) - } - - fun getReferenceDefinition(databaseName: TypeName?, typeName: TypeName?): EntityDefinition? { - return getTableDefinition(databaseName, typeName) - ?: getQueryModelDefinition(databaseName, typeName) - ?: getModelViewDefinition(databaseName, typeName) - } - - fun addModelViewDefinition(modelViewDefinition: ModelViewDefinition) { - modelViewDefinition.elementClassName?.let { - getOrPutDatabase(modelViewDefinition.associationalBehavior.databaseTypeName) - ?.modelViewDefinitionMap?.put(it, modelViewDefinition) - } - } - - fun getTypeConverters() = typeConverters.values.toHashSet().sortedBy { it.modelTypeName?.toString() } - - fun getTableDefinitions(databaseName: TypeName): List { - val databaseHolderDefinition = getOrPutDatabase(databaseName) - return (databaseHolderDefinition?.tableDefinitionMap?.values ?: arrayListOf()) - .toHashSet() - .sortedBy { it.outputClassName?.simpleName() } - } - - fun setTableDefinitions(tableDefinitionSet: MutableMap, databaseName: TypeName) { - val databaseDefinition = getOrPutDatabase(databaseName) - databaseDefinition?.tableDefinitionMap = tableDefinitionSet - } - - fun getModelViewDefinitions(databaseName: TypeName): List { - val databaseDefinition = getOrPutDatabase(databaseName) - return (databaseDefinition?.modelViewDefinitionMap?.values ?: arrayListOf()) - .toHashSet() - .sortedBy { it.outputClassName?.simpleName() } - .sortedByDescending { it.priority } - } - - fun setModelViewDefinitions(modelViewDefinitionMap: MutableMap, elementClassName: ClassName) { - val databaseDefinition = getOrPutDatabase(elementClassName) - databaseDefinition?.modelViewDefinitionMap = modelViewDefinitionMap - } - - fun getQueryModelDefinitions(databaseName: TypeName): List { - val databaseDefinition = getOrPutDatabase(databaseName) - return (databaseDefinition?.queryModelDefinitionMap?.values ?: arrayListOf()) - .toHashSet() - .sortedBy { it.outputClassName?.simpleName() } - } - - fun addMigrationDefinition(migrationDefinition: MigrationDefinition) { - val migrationDefinitionMap = migrations.getOrPut(migrationDefinition.databaseName) { hashMapOf() } - val migrationDefinitions = migrationDefinitionMap.getOrPut(migrationDefinition.version) { arrayListOf() } - if (!migrationDefinitions.contains(migrationDefinition)) { - migrationDefinitions.add(migrationDefinition) - } - } - - fun getMigrationsForDatabase(databaseName: TypeName) = migrations[databaseName] - ?: hashMapOf>() - - fun addContentProviderDefinition(contentProviderDefinition: ContentProviderDefinition) { - contentProviderDefinition.elementTypeName?.let { - val holderDefinition = getOrPutDatabase(contentProviderDefinition.databaseTypeName) - holderDefinition?.providerMap?.put(it, contentProviderDefinition) - providerMap.put(it, contentProviderDefinition) - } - } - - fun putTableEndpointForProvider(tableEndpointDefinition: TableEndpointDefinition) { - val contentProviderDefinition = providerMap[tableEndpointDefinition.contentProviderName] - if (contentProviderDefinition == null) { - logError("Content Provider ${tableEndpointDefinition.contentProviderName} was not found for the @TableEndpoint ${tableEndpointDefinition.elementClassName}") - } else { - contentProviderDefinition.endpointDefinitions.add(tableEndpointDefinition) - } - } - - fun logError(callingClass: KClass<*>?, error: String?, vararg args: Any?) { - messager.printMessage(Diagnostic.Kind.ERROR, - String.format("${ - (callingClass?.toString() ?: "") - // don't print this in logs. - .replace("(Kotlin reflection is not available)", "") - } : ${error?.trim()}", *args)) - } - - fun logError(error: String?) = logError(callingClass = null, error = error) - - fun logWarning(error: String?) { - messager.printMessage(Diagnostic.Kind.WARNING, error ?: "") - } - - fun logWarning(callingClass: Class<*>, error: String) { - logWarning("$callingClass : $error") - } - - private fun getOrPutDatabase(databaseName: TypeName?): DatabaseObjectHolder? = - databaseDefinitionMap.getOrPut(databaseName) { DatabaseObjectHolder() } - - override fun handle(processorManager: ProcessorManager, roundEnvironment: RoundEnvironment) { - handlers.forEach { it.handle(processorManager, roundEnvironment) } - - val databaseDefinitions = getDatabaseHolderDefinitionList() - .sortedBy { it.databaseDefinition?.outputClassName?.simpleName() } - for (databaseHolderDefinition in databaseDefinitions) { - try { - - if (databaseHolderDefinition.databaseDefinition == null) { - manager.logError(databaseHolderDefinition.getMissingDBRefs().joinToString("\n")) - continue - } - - val manyToManyDefinitions = databaseHolderDefinition.manyToManyDefinitionMap.values - - val flattenedList = manyToManyDefinitions.flatten().sortedBy { it.outputClassName?.simpleName() } - for (manyToManyList in flattenedList) { - manyToManyList.prepareForWrite() - manyToManyList.writeBaseDefinition(processorManager) - } - - // process all in next round. - if (!manyToManyDefinitions.isEmpty()) { - manyToManyDefinitions.clear() - continue - } - - if (roundEnvironment.processingOver()) { - val validator = ContentProviderValidator() - val contentProviderDefinitions = databaseHolderDefinition.providerMap.values - .sortedBy { it.outputClassName?.simpleName() } - contentProviderDefinitions.forEach { contentProviderDefinition -> - if (validator.validate(processorManager, contentProviderDefinition)) { - contentProviderDefinition.writeBaseDefinition(processorManager) - } - } - } - - databaseHolderDefinition.databaseDefinition?.validateAndPrepareToWrite() - - if (roundEnvironment.processingOver()) { - databaseHolderDefinition.databaseDefinition?.let { - if (it.outputClassName != null) { - JavaFile.builder(it.packageName, it.typeSpec).build() - .writeTo(processorManager.processingEnvironment.filer) - } - } - } - - val tableDefinitions = databaseHolderDefinition.tableDefinitionMap.values - .sortedBy { it.outputClassName?.simpleName() } - - tableDefinitions.forEach { it.writeBaseDefinition(processorManager) } - - val modelViewDefinitions = databaseHolderDefinition.modelViewDefinitionMap.values - modelViewDefinitions - .sortedByDescending { it.priority } - .forEach { it.writeBaseDefinition(processorManager) } - - val queryModelDefinitions = databaseHolderDefinition.queryModelDefinitionMap.values - .sortedBy { it.outputClassName?.simpleName() } - queryModelDefinitions.forEach { it.writeBaseDefinition(processorManager) } - - tableDefinitions.safeWritePackageHelper(processorManager) - modelViewDefinitions.safeWritePackageHelper(processorManager) - queryModelDefinitions.safeWritePackageHelper(processorManager) - } catch (e: IOException) { - } - - } - - try { - val databaseHolderDefinition = DatabaseHolderDefinition(processorManager) - if (!databaseHolderDefinition.isGarbage()) { - JavaFile.builder(com.dbflow5.processor.ClassNames.FLOW_MANAGER_PACKAGE, - databaseHolderDefinition.typeSpec).build() - .writeTo(processorManager.processingEnvironment.filer) - } - } catch (e: FilerException) { - } catch (e: IOException) { - logError(e.message) - } - } - - fun elementBelongsInTable(element: Element): Boolean { - val enclosingElement = element.enclosingElement - var find: EntityDefinition? = databaseDefinitionMap.values.flatMap { it.tableDefinitionMap.values } - .find { it.element == enclosingElement } - - // modelview check. - if (find == null) { - find = databaseDefinitionMap.values.flatMap { it.modelViewDefinitionMap.values } - .find { it.element == enclosingElement } - } - // querymodel check - if (find == null) { - find = databaseDefinitionMap.values.flatMap { it.queryModelDefinitionMap.values } - .find { it.element == enclosingElement } - } - return find != null - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/SQLiteHelper.kt b/processor/src/main/kotlin/com/dbflow5/processor/SQLiteHelper.kt deleted file mode 100644 index 6d56ccff4a1c2b8b5681f6f974ef246cb5c6ee34..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/SQLiteHelper.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.data.Blob -import com.squareup.javapoet.ArrayTypeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.TypeName - -/** - * Author: andrewgrosner - * Description: Holds the mapping between SQL data types and java classes used in the processor. - */ -enum class SQLiteHelper { - - INTEGER { - override val sqLiteStatementMethod = "Long" - - override val sqliteStatementWrapperMethod: String - get() = "Number" - }, - REAL { - override val sqLiteStatementMethod = "Double" - }, - TEXT { - override val sqLiteStatementMethod = "String" - }, - BLOB { - override val sqLiteStatementMethod = "Blob" - }; - - abstract val sqLiteStatementMethod: String - - open val sqliteStatementWrapperMethod - get() = sqLiteStatementMethod - - companion object { - - private val sTypeMap = hashMapOf(TypeName.BYTE to INTEGER, - TypeName.SHORT to INTEGER, - TypeName.INT to INTEGER, - TypeName.LONG to INTEGER, - TypeName.FLOAT to REAL, - TypeName.DOUBLE to REAL, - TypeName.BOOLEAN to INTEGER, - TypeName.CHAR to TEXT, - ArrayTypeName.of(TypeName.BYTE) to BLOB, - TypeName.BYTE.box() to INTEGER, - TypeName.SHORT.box() to INTEGER, - TypeName.INT.box() to INTEGER, - TypeName.LONG.box() to INTEGER, - TypeName.FLOAT.box() to REAL, - TypeName.DOUBLE.box() to REAL, - TypeName.BOOLEAN.box() to INTEGER, - TypeName.CHAR.box() to TEXT, - ClassName.get(String::class.java) to TEXT, - ArrayTypeName.of(TypeName.BYTE.box()) to BLOB, - ArrayTypeName.of(TypeName.BYTE) to BLOB, - ClassName.get(Blob::class.java) to BLOB) - - private val sMethodMap = hashMapOf(ArrayTypeName.of(TypeName.BYTE) to "getBlob", - ArrayTypeName.of(TypeName.BYTE.box()) to "getBlob", - TypeName.BOOLEAN to "getBoolean", - TypeName.BYTE to "getInt", - TypeName.BYTE.box() to "getInt", - TypeName.CHAR to "getString", - TypeName.CHAR.box() to "getString", - TypeName.DOUBLE to "getDouble", - TypeName.DOUBLE.box() to "getDouble", - TypeName.FLOAT to "getFloat", - TypeName.FLOAT.box() to "getFloat", - TypeName.INT to "getInt", - TypeName.INT.box() to "getInt", - TypeName.LONG to "getLong", - TypeName.LONG.box() to "getLong", - TypeName.SHORT to "getShort", - TypeName.SHORT.box() to "getShort", - ClassName.get(String::class.java) to "getString", - ClassName.get(Blob::class.java) to "getBlob") - - private val sNumberMethodList = hashSetOf(TypeName.BYTE, TypeName.DOUBLE, TypeName.FLOAT, - TypeName.LONG, TypeName.SHORT, TypeName.INT) - - operator fun get(typeName: TypeName?): SQLiteHelper = sTypeMap[typeName] - ?: throw IllegalArgumentException("Cannot map $typeName to a SQLite Type. If this is a " + - "TypeConverter, ensure it maps to a primitive type.") - - fun getWrapperMethod(typeName: TypeName?): String { - var sqLiteHelper = get(typeName).sqliteStatementWrapperMethod - if (typeName == TypeName.FLOAT.box()) { - sqLiteHelper = "Float" - } - return sqLiteHelper - } - - fun containsType(typeName: TypeName?): Boolean = sTypeMap.containsKey(typeName) - - fun containsMethod(typeName: TypeName?): Boolean = sMethodMap.containsKey(typeName) - - fun getMethod(typeName: TypeName?): String = sMethodMap[typeName] ?: "" - - fun containsNumberMethod(typeName: TypeName?): Boolean = sNumberMethodList.contains(typeName) - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/Validators.kt b/processor/src/main/kotlin/com/dbflow5/processor/Validators.kt deleted file mode 100644 index 28213b1cce07c3dffd18ab795786cf2fa89b4c3d..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/Validators.kt +++ /dev/null @@ -1,264 +0,0 @@ -package com.dbflow5.processor - -import com.dbflow5.processor.definition.DatabaseDefinition -import com.dbflow5.processor.definition.ModelViewDefinition -import com.dbflow5.processor.definition.OneToManyDefinition -import com.dbflow5.processor.definition.TableDefinition -import com.dbflow5.processor.definition.TypeConverterDefinition -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.EnumColumnAccessor -import com.dbflow5.processor.definition.column.PrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.ReferenceColumnDefinition -import com.dbflow5.processor.definition.provider.ContentProviderDefinition -import com.dbflow5.processor.definition.provider.TableEndpointDefinition -import com.dbflow5.processor.utils.isNullOrEmpty - - -/** - * Description: the base interface for validating annotations. - */ -interface Validator { - - /** - * @param processorManager The manager - * * - * @param validatorDefinition The validator to use - * * - * @return true if validation passed, false if there was an error. - */ - fun validate(processorManager: ProcessorManager, validatorDefinition: ValidatorDefinition): Boolean -} - - -/** - * Description: Ensures the integrity of the annotation processor for columns. - * @author Andrew Grosner (fuzz) - */ -class ColumnValidator : Validator { - - private var autoIncrementingPrimaryKey: ColumnDefinition? = null - - override fun validate(processorManager: ProcessorManager, validatorDefinition: ColumnDefinition): Boolean { - - var success = true - - // validate getter and setters. - if (validatorDefinition.columnAccessor is PrivateScopeColumnAccessor) { - val privateColumnAccess = validatorDefinition.columnAccessor as PrivateScopeColumnAccessor - if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getterNameElement)) { - processorManager.logError(ColumnValidator::class, - """Could not find getter for private element: "${validatorDefinition.elementName}" - | from table class: ${validatorDefinition.entityDefinition.elementName}. - | Consider adding a getter with name ${privateColumnAccess.getterNameElement}, - | making it more accessible, or adding a @get:JvmName("${privateColumnAccess.getterNameElement}") """ - .trimMargin()) - success = false - } - if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.setterNameElement)) { - processorManager.logError(ColumnValidator::class, - """Could not find setter for private element: "${validatorDefinition.elementName}" - | from table class: ${validatorDefinition.entityDefinition.elementName}. - | Consider adding a setter with name ${privateColumnAccess.setterNameElement}, - | making it more accessible, or adding a @set:JvmName("${privateColumnAccess.setterNameElement}").""" - .trimMargin()) - success = false - } - } - - if (!validatorDefinition.defaultValue.isNullOrEmpty()) { - val typeName = validatorDefinition.elementTypeName - if (validatorDefinition is ReferenceColumnDefinition && validatorDefinition.isReferencingTableObject) { - processorManager.logError(ColumnValidator::class, - "Default values cannot be specified for model fields") - } else if (typeName?.isPrimitive == true) { - processorManager.logWarning(ColumnValidator::class.java, - "Default value of ${validatorDefinition.defaultValue} from" + - " ${validatorDefinition.entityDefinition.elementName}.${validatorDefinition.elementName}" + - " is ignored for primitive columns.") - } - } - - if (validatorDefinition.columnName.isEmpty()) { - success = false - processorManager.logError("Field ${validatorDefinition.elementName} " + - "cannot have a null column name for column: ${validatorDefinition.columnName}" + - " and type: ${validatorDefinition.elementTypeName}") - } - - if (validatorDefinition.columnAccessor is EnumColumnAccessor) { - if (validatorDefinition.type is ColumnDefinition.Type.Primary) { - success = false - processorManager.logError("Enums cannot be primary keys. Column: ${validatorDefinition.columnName}" + - " and type: ${validatorDefinition.elementTypeName}") - } else if (validatorDefinition is ReferenceColumnDefinition) { - success = false - processorManager.logError("Enums cannot be foreign keys. Column: ${validatorDefinition.columnName}" + - " and type: ${validatorDefinition.elementTypeName}") - } - } - - if (validatorDefinition is ReferenceColumnDefinition) { - validatorDefinition.column?.let { - if (it.name.isNotEmpty()) { - success = false - processorManager.logError("Foreign Key ${validatorDefinition.elementName} cannot specify the @Column.name() field. " - + "Use a @ForeignKeyReference(columnName = {NAME} instead. " + - "Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}") - } - } - - // it is an error to specify both a not null and provide explicit references. - if (validatorDefinition.explicitReferences && validatorDefinition.notNull) { - success = false - processorManager.logError("Foreign Key ${validatorDefinition.elementName} " + - "cannot specify both @NotNull and references. Remove the top-level @NotNull " + - "and use the contained 'notNull' field " + - "in each reference to control its SQL notnull conflicts.") - } - - } else { - if (autoIncrementingPrimaryKey != null && validatorDefinition.type is ColumnDefinition.Type.Primary) { - processorManager.logError("You cannot mix and match autoincrementing and composite primary keys.") - success = false - } - - if (validatorDefinition.type is ColumnDefinition.Type.PrimaryAutoIncrement - || validatorDefinition.type is ColumnDefinition.Type.RowId) { - if (autoIncrementingPrimaryKey == null) { - autoIncrementingPrimaryKey = validatorDefinition - } else if (autoIncrementingPrimaryKey != validatorDefinition) { - processorManager.logError("Only one auto-incrementing primary key is allowed on a table. " + - "Found Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}") - success = false - } - } - } - - return success - } -} - -/** - * Description: - */ -class ContentProviderValidator : Validator { - override fun validate(processorManager: ProcessorManager, - validatorDefinition: ContentProviderDefinition): Boolean { - var success = true - - if (validatorDefinition.endpointDefinitions.isEmpty()) { - processorManager.logError("The content provider ${validatorDefinition.element.simpleName} " + - "must have at least 1 @TableEndpoint associated with it") - success = false - } - - return success - } -} - -/** - * Description: - */ -class DatabaseValidator : Validator { - override fun validate(processorManager: ProcessorManager, - validatorDefinition: DatabaseDefinition): Boolean = true -} - -/** - * Description: - */ -class ModelViewValidator : Validator { - - override fun validate(processorManager: ProcessorManager, validatorDefinition: ModelViewDefinition): Boolean = true -} - -/** - * Description: Validates to ensure a [OneToManyDefinition] is correctly coded. Will throw failures on the [ProcessorManager] - */ -class OneToManyValidator : Validator { - override fun validate(processorManager: ProcessorManager, validatorDefinition: OneToManyDefinition): Boolean = true -} - -class TableEndpointValidator : Validator { - - override fun validate(processorManager: ProcessorManager, validatorDefinition: TableEndpointDefinition): Boolean { - var success = true - - if (validatorDefinition.contentUriDefinitions.isEmpty()) { - processorManager.logError("A table endpoint ${validatorDefinition.elementClassName} " + - "must supply at least one @ContentUri") - success = false - } - - return success - } -} - -/** - * Description: Validates proper usage of the [com.dbflow5.annotation.Table] - */ -class TableValidator : Validator { - - override fun validate(processorManager: ProcessorManager, validatorDefinition: TableDefinition): Boolean { - var success = true - - if (!validatorDefinition.hasPrimaryConstructor) { - processorManager.logError(TableValidator::class, "Table ${validatorDefinition.elementClassName}" + - " must provide a visible, parameterless constructor. Each field also must have a visible " + - "setter for now.") - success = false - } - - if (validatorDefinition.columnDefinitions.isEmpty()) { - processorManager.logError(TableValidator::class, "Table ${validatorDefinition.associationalBehavior.name} " + - "of ${validatorDefinition.elementClassName}, ${validatorDefinition.element.javaClass} " + - "needs to define at least one column") - success = false - } - - val hasTwoKinds = (validatorDefinition.primaryKeyColumnBehavior.hasAutoIncrement - || validatorDefinition.primaryKeyColumnBehavior.hasRowID) - && !validatorDefinition._primaryColumnDefinitions.isEmpty() - - if (hasTwoKinds) { - processorManager.logError(TableValidator::class, "Table ${validatorDefinition.associationalBehavior.name}" + - " cannot mix and match autoincrement and composite primary keys") - success = false - } - - val hasPrimary = (validatorDefinition.primaryKeyColumnBehavior.hasAutoIncrement - || validatorDefinition.primaryKeyColumnBehavior.hasRowID) - && validatorDefinition._primaryColumnDefinitions.isEmpty() - || !validatorDefinition.primaryKeyColumnBehavior.hasAutoIncrement - && !validatorDefinition.primaryKeyColumnBehavior.hasRowID - && !validatorDefinition._primaryColumnDefinitions.isEmpty() - if (!hasPrimary && validatorDefinition.type == TableDefinition.Type.Normal) { - processorManager.logError(TableValidator::class, "Table ${validatorDefinition.associationalBehavior.name} " + - "needs to define at least one primary key") - success = false - } - - return success - } -} - -class TypeConverterValidator : Validator { - override fun validate(processorManager: ProcessorManager, - validatorDefinition: TypeConverterDefinition): Boolean { - var success = true - - if (validatorDefinition.modelTypeName == null) { - processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + - "unsupported Model Element parameter. If it has type parameters, you must " + - "remove them or subclass it for proper usage.") - success = false - } else if (validatorDefinition.dbTypeName == null) { - processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + - "unsupported DB Element parameter. If it has type parameters, you must remove" + - " them or subclass it for proper usage.") - success = false - } - - return success - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/Adders.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/Adders.kt deleted file mode 100644 index 031123b2a176222e496de72f800247d92f7dc854..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/Adders.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dbflow5.processor.definition - -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeSpec - -/** - * Description: - * - * @author Andrew Grosner (fuzz) - */ -interface TypeAdder { - - fun addToType(typeBuilder: TypeSpec.Builder) -} - -interface CodeAdder { - - fun addCode(code: CodeBlock.Builder): CodeBlock.Builder -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/BaseDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/BaseDefinition.kt deleted file mode 100644 index 647812b6fe101612e5435cf46a2faf8eb2cec427..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/BaseDefinition.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.DBFlowProcessor -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.utils.hasJavaX -import com.dbflow5.processor.utils.toClassName -import com.dbflow5.processor.utils.toTypeElement -import com.grosner.kpoet.S -import com.grosner.kpoet.`@` -import com.grosner.kpoet.`public final class` -import com.grosner.kpoet.extends -import com.grosner.kpoet.implements -import com.grosner.kpoet.javadoc -import com.grosner.kpoet.typeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.Element -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.TypeElement - -/** - * Description: Holds onto a common-set of fields and provides a common-set of methods to output class files. - */ -abstract class BaseDefinition( - /** - * Original definition element. - */ - val element: Element, - - /** - * Optional [TypeElement]. if the [element] passed in is a type. - */ - val typeElement: TypeElement?, - /** - * The resolved [TypeName] for this definition. It may be a return type if [ExecutableElement], - * - */ - val elementTypeName: TypeName?, - val manager: ProcessorManager, - val packageName: String) : TypeDefinition { - - - /** - * The [ClassName] referring to the type of the definition. This excludes primitives. - */ - var elementClassName: ClassName? = null - - - var outputClassName: ClassName? = null - private set - - /** - * Unqualified name of the [element]. Useful for names of methods, fields, or short type names. - */ - val elementName: String = element.simpleName.toString() - - constructor(element: ExecutableElement, processorManager: ProcessorManager) : this( - manager = processorManager, - packageName = processorManager.elements.getPackageOf(element)?.qualifiedName?.toString() - ?: "", - element = element, - typeElement = null, - elementTypeName = try { - element.asType().typeName - } catch (i: IllegalArgumentException) { - // unexpected TypeMirror (usually a List). Cannot use for TypeName. - null - } - ) { - elementClassName = if (elementTypeName?.isPrimitive == true) null else element.toClassName(manager) - } - - constructor(element: Element, processorManager: ProcessorManager, - packageName: String = processorManager.elements.getPackageOf(element)?.qualifiedName?.toString() - ?: "") : this( - manager = processorManager, - element = element, - packageName = packageName, - typeElement = element as? TypeElement ?: element.toTypeElement(), - elementTypeName = try { - when (element) { - is ExecutableElement -> element.returnType - else -> element.asType() - }.typeName - } catch (i: IllegalArgumentException) { - processorManager.logError("Found illegal type: ${element.asType()} for ${element.simpleName}") - processorManager.logError("Exception here: $i") - null - } - ) { - elementTypeName?.let { - if (!it.isPrimitive) elementClassName = element.toClassName(processorManager) - } - - } - - constructor(element: TypeElement, processorManager: ProcessorManager) : this( - manager = processorManager, - element = element, - typeElement = element, - packageName = processorManager.elements.getPackageOf(element)?.qualifiedName?.toString() - ?: "", - elementTypeName = element.asType().typeName - ) { - elementClassName = element.toClassName(processorManager) - } - - protected fun setOutputClassName(postfix: String) { - val outputName: String - - val elementClassName = elementClassName - if (elementClassName == null) { - when (elementTypeName) { - is ClassName -> outputName = elementTypeName.simpleName() - is ParameterizedTypeName -> { - outputName = elementTypeName.rawType.simpleName() - this.elementClassName = elementTypeName.rawType - } - else -> outputName = elementTypeName.toString() - } - } else { - outputName = elementClassName.simpleName() - } - outputClassName = ClassName.get(packageName, outputName + postfix) - } - - protected fun setOutputClassNameFull(fullName: String) { - outputClassName = ClassName.get(packageName, fullName) - } - - override val typeSpec: TypeSpec - get() { - if (outputClassName == null) { - manager.logError("$elementTypeName's is missing an outputClassName. Database was " + - "${(this as? EntityDefinition)?.associationalBehavior?.databaseTypeName}") - } - return `public final class`(outputClassName?.simpleName() ?: "") { - if (hasJavaX()) { - addAnnotation(`@`(ClassNames.GENERATED) { - this["value"] = DBFlowProcessor::class.java.canonicalName.toString().S - }.build()) - } - extendsClass?.let { extends(it) } - implementsClasses.forEach { implements(it) } - javadoc("This is generated code. Please do not modify") - onWriteDefinition(this) - this - } - } - - protected open val extendsClass: TypeName? - get() = null - - protected val implementsClasses: Array - get() = arrayOf() - - open fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseDefinition.kt deleted file mode 100644 index 0bfea8bc10b6d6499c9b2cc34a7bbe490ec988dd..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseDefinition.kt +++ /dev/null @@ -1,164 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.Database -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ModelViewValidator -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.TableValidator -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.isSubclass -import com.grosner.kpoet.L -import com.grosner.kpoet.`return` -import com.grosner.kpoet.constructor -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import com.squareup.javapoet.WildcardTypeName -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier - -/** - * Description: Writes [Database] definitions, - * which contain [Table], [ModelView], and [Migration] - */ -class DatabaseDefinition(database: Database, - manager: ProcessorManager, element: Element) - : BaseDefinition(element, manager, packageName = ClassNames.FLOW_MANAGER_PACKAGE), TypeDefinition { - - private val databaseVersion: Int = database.version - private val foreignKeysSupported = database.foreignKeyConstraintsEnforced - private val consistencyChecksEnabled = database.consistencyCheckEnabled - private val backupEnabled = database.backupEnabled - - val insertConflict: ConflictAction = database.insertConflict - val updateConflict: ConflictAction = database.updateConflict - - var objectHolder: DatabaseObjectHolder? = null - - init { - setOutputClassName("${elementName}_Database") - - if (!element.modifiers.contains(Modifier.ABSTRACT) - || element.modifiers.contains(Modifier.PRIVATE) - || !typeElement.isSubclass(manager.processingEnvironment, ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME)) { - manager.logError("$elementClassName must be a visible abstract class that " + - "extends ${ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME}") - } - } - - override val extendsClass: TypeName? = elementClassName - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - writeConstructor(typeBuilder) - writeGetters(typeBuilder) - } - - fun validateAndPrepareToWrite() { - prepareDefinitions() - validateDefinitions() - } - - private fun validateDefinitions() { - elementClassName?.let { className -> - val map = hashMapOf() - val tableValidator = TableValidator() - manager.getTableDefinitions(className) - .filter { tableValidator.validate(ProcessorManager.manager, it) } - .forEach { it.elementClassName?.let { className -> map.put(className, it) } } - manager.setTableDefinitions(map, className) - - val modelViewDefinitionMap = hashMapOf() - val modelViewValidator = ModelViewValidator() - manager.getModelViewDefinitions(className) - .filter { modelViewValidator.validate(ProcessorManager.manager, it) } - .forEach { it.elementClassName?.let { className -> modelViewDefinitionMap.put(className, it) } } - manager.setModelViewDefinitions(modelViewDefinitionMap, className) - } - } - - private fun prepareDefinitions() { - elementClassName?.let { className -> - manager.getTableDefinitions(className).forEach(TableDefinition::prepareForWrite) - manager.getModelViewDefinitions(className).forEach(ModelViewDefinition::prepareForWrite) - manager.getQueryModelDefinitions(className).forEach(QueryModelDefinition::prepareForWrite) - } - } - - private fun writeConstructor(builder: TypeSpec.Builder) { - - builder.constructor(param(ClassNames.DATABASE_HOLDER, "holder")) { - modifiers(public) - this@DatabaseDefinition.elementClassName?.let { elementClassName -> - for (definition in manager.getTableDefinitions(elementClassName)) { - if (definition.hasGlobalTypeConverters) { - statement("addModelAdapter(new \$T(holder, this), holder)", definition.outputClassName) - } else { - statement("addModelAdapter(new \$T(this), holder)", definition.outputClassName) - } - } - - for (definition in manager.getModelViewDefinitions(elementClassName)) { - if (definition.hasGlobalTypeConverters) { - statement("addModelViewAdapter(new \$T(holder, this), holder)", definition.outputClassName) - } else { - statement("addModelViewAdapter(new \$T(this), holder)", definition.outputClassName) - } - } - - for (definition in manager.getQueryModelDefinitions(elementClassName)) { - if (definition.hasGlobalTypeConverters) { - statement("addRetrievalAdapter(new \$T(holder, this), holder)", definition.outputClassName) - } else { - statement("addRetrievalAdapter(new \$T(this), holder)", definition.outputClassName) - } - } - - val migrationDefinitionMap = manager.getMigrationsForDatabase(elementClassName) - migrationDefinitionMap.keys - .sortedByDescending { it } - .forEach { version -> - migrationDefinitionMap[version] - ?.sortedBy { it.priority } - ?.forEach { migrationDefinition -> - statement("addMigration($version, new \$T${migrationDefinition.constructorName})", migrationDefinition.elementClassName) - } - } - } - this - } - - } - - private fun writeGetters(typeBuilder: TypeSpec.Builder) { - typeBuilder.apply { - `override fun`(ParameterizedTypeName.get(ClassName.get(Class::class.java), WildcardTypeName.subtypeOf(Any::class.java)), - "getAssociatedDatabaseClassFile") { - modifiers(public, final) - `return`("\$T.class", elementTypeName) - } - `override fun`(TypeName.BOOLEAN, "isForeignKeysSupported") { - modifiers(public, final) - `return`(foreignKeysSupported.L) - } - `override fun`(TypeName.BOOLEAN, "backupEnabled") { - modifiers(public, final) - `return`(backupEnabled.L) - } - `override fun`(TypeName.BOOLEAN, "areConsistencyChecksEnabled") { - modifiers(public, final) - `return`(consistencyChecksEnabled.L) - } - `override fun`(TypeName.INT, "getDatabaseVersion") { - modifiers(public, final) - `return`(databaseVersion.L) - } - } - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseHolderDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseHolderDefinition.kt deleted file mode 100644 index 982c9aa8675eb4c1b31d528e8096df3ad22dd269..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseHolderDefinition.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.DatabaseHandler -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.utils.rawTypeName -import com.grosner.kpoet.`public final class` -import com.grosner.kpoet.constructor -import com.grosner.kpoet.extends -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.TypeSpec - -/** - * Description: Top-level writer that handles writing all [DatabaseDefinition] - * and [com.dbflow5.annotation.TypeConverter] - */ -class DatabaseHolderDefinition(private val processorManager: ProcessorManager) : TypeDefinition { - - val className: String - - init { - - var _className = "" - val options = this.processorManager.processingEnvironment.options - if (options.containsKey(OPTION_TARGET_MODULE_NAME)) { - _className = options[OPTION_TARGET_MODULE_NAME] ?: "" - } - - _className += ClassNames.DATABASE_HOLDER_STATIC_CLASS_NAME - - className = _className - } - - override val typeSpec: TypeSpec = `public final class`(this.className) { - extends(ClassNames.DATABASE_HOLDER) - - constructor { - modifiers(public) - - processorManager.getTypeConverters().forEach { tc -> - statement("\$L.put(\$T.class, new \$T())", - DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, - tc.modelTypeName?.rawTypeName(), - tc.className) - - tc.allowedSubTypes.forEach { subType -> - statement("\$L.put(\$T.class, new \$T())", - DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, - subType.rawTypeName(), tc.className) - } - } - - processorManager.getDatabaseHolderDefinitionList() - .asSequence() - .mapNotNull { it.databaseDefinition?.outputClassName } - .sortedBy { it.simpleName() } - .forEach { statement("new \$T(this)", it) } - this - } - } - - /** - * If none of the database holder databases exist, don't generate a holder. - */ - fun isGarbage() = processorManager.getDatabaseHolderDefinitionList() - .none { it.databaseDefinition?.outputClassName != null } - - companion object { - - const val OPTION_TARGET_MODULE_NAME = "targetModuleName" - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseObjectHolder.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseObjectHolder.kt deleted file mode 100644 index 8a33338284aa2cac7efa2863106d2dd44f31f1ee..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/DatabaseObjectHolder.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.processor.definition.provider.ContentProviderDefinition -import com.squareup.javapoet.TypeName - -/** - * Description: Provides overarching holder for [DatabaseDefinition], [TableDefinition], - * and more. So we can safely use. - */ -class DatabaseObjectHolder { - - var databaseDefinition: DatabaseDefinition? = null - set(databaseDefinition) { - field = databaseDefinition - field?.objectHolder = this - } - - var tableDefinitionMap: MutableMap = hashMapOf() - val tableNameMap: MutableMap = hashMapOf() - - val queryModelDefinitionMap: MutableMap = hashMapOf() - var modelViewDefinitionMap: MutableMap = hashMapOf() - val manyToManyDefinitionMap: MutableMap> = hashMapOf() - val providerMap = hashMapOf() - - /** - * Retrieve what database class they're trying to reference. - */ - fun getMissingDBRefs(): List { - if (databaseDefinition == null) { - val list = mutableListOf() - tableDefinitionMap.values.forEach { - list += "Database ${it.associationalBehavior.databaseTypeName} not found for Table ${it.associationalBehavior.name}" - } - queryModelDefinitionMap.values.forEach { - list += "Database ${it.associationalBehavior.databaseTypeName} not found for QueryModel ${it.elementName}" - } - modelViewDefinitionMap.values.forEach { - list += "Database ${it.associationalBehavior.databaseTypeName} not found for ModelView ${it.elementName}" - } - providerMap.values.forEach { - list += "Database ${it.databaseTypeName} not found for ContentProvider ${it.elementName}" - } - return list - - } else { - return listOf() - } - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/EntityDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/EntityDefinition.kt deleted file mode 100644 index a5c04aaa1d56ce8cbacba7b820561c7508064586..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/EntityDefinition.kt +++ /dev/null @@ -1,207 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.behavior.AssociationalBehavior -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.definition.behavior.PrimaryKeyColumnBehavior -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.PackagePrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.ReferenceColumnDefinition -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.ModelUtils -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.getPackage -import com.dbflow5.processor.utils.implementsClass -import com.dbflow5.processor.utils.toClassName -import com.grosner.kpoet.`public static final` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.code -import com.grosner.kpoet.constructor -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import java.io.IOException -import javax.annotation.processing.FilerException -import javax.annotation.processing.ProcessingEnvironment -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement - -fun Collection.safeWritePackageHelper(processorManager: ProcessorManager) = forEach { - try { - it.writePackageHelper(processorManager.processingEnvironment) - } catch (e: FilerException) { /*Ignored intentionally to allow multi-round table generation*/ - } -} - -/** - * Description: Used to write Models and ModelViews - */ -abstract class EntityDefinition(typeElement: TypeElement, processorManager: ProcessorManager) - : BaseDefinition(typeElement, processorManager) { - - var columnDefinitions: MutableList = arrayListOf() - protected set - - val sqlColumnDefinitions - get() = columnDefinitions.filter { it.type !is ColumnDefinition.Type.RowId } - - val associatedTypeConverters = hashMapOf>() - val globalTypeConverters = hashMapOf>() - val packagePrivateList = arrayListOf() - - val classElementLookUpMap: MutableMap = mutableMapOf() - - val modelClassName = typeElement.simpleName.toString() - val databaseDefinition: DatabaseDefinition by lazy { - manager.getDatabaseHolderDefinition(associationalBehavior.databaseTypeName)?.databaseDefinition - ?: throw RuntimeException("DatabaseDefinition not found for DB element named ${associationalBehavior.name}" + - " for db type: ${associationalBehavior.databaseTypeName}.") - } - - val hasGlobalTypeConverters - get() = globalTypeConverters.isNotEmpty() - - val implementsLoadFromCursorListener = typeElement.implementsClass(manager.processingEnvironment, - ClassNames.LOAD_FROM_CURSOR_LISTENER) - - abstract val associationalBehavior: AssociationalBehavior - abstract val cursorHandlingBehavior: CursorHandlingBehavior - open var primaryKeyColumnBehavior: PrimaryKeyColumnBehavior = - PrimaryKeyColumnBehavior(hasRowID = false, associatedColumn = null, hasAutoIncrement = false) - - abstract val methods: Array - - protected abstract fun createColumnDefinitions(typeElement: TypeElement) - - abstract val primaryColumnDefinitions: List - - fun prepareForWrite() { - classElementLookUpMap.clear() - columnDefinitions.clear() - packagePrivateList.clear() - - prepareForWriteInternal() - } - - protected abstract fun prepareForWriteInternal() - - val parameterClassName: TypeName? - get() = elementClassName - - fun addColumnForCustomTypeConverter(columnDefinition: ColumnDefinition, typeConverterName: ClassName): String { - val columnDefinitions = associatedTypeConverters.getOrPut(typeConverterName) { arrayListOf() } - columnDefinitions.add(columnDefinition) - return "typeConverter${typeConverterName.simpleName()}" - } - - fun addColumnForTypeConverter(columnDefinition: ColumnDefinition, typeConverterName: ClassName): String { - val columnDefinitions = globalTypeConverters.getOrPut(typeConverterName) { arrayListOf() } - columnDefinitions.add(columnDefinition) - return "global_typeConverter${typeConverterName.simpleName()}" - } - - fun TypeSpec.Builder.writeConstructor() { - val customTypeConverterPropertyMethod = CustomTypeConverterPropertyMethod(this@EntityDefinition) - customTypeConverterPropertyMethod.addToType(this) - - constructor { - if (hasGlobalTypeConverters) { - addParameter(param(ClassNames.DATABASE_HOLDER, "holder").build()) - } - addParameter(param(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition").build()) - modifiers(public) - statement("super(databaseDefinition)") - code { - customTypeConverterPropertyMethod.addCode(this) - } - } - } - - fun writeGetModelClass(typeBuilder: TypeSpec.Builder, modelClassName: ClassName?) = typeBuilder.apply { - `override fun`(ParameterizedTypeName.get(ClassName.get(Class::class.java), modelClassName), "getTable") { - modifiers(public, final) - `return`("\$T.class", modelClassName) - } - } - - @Throws(IOException::class) - fun writePackageHelper(processingEnvironment: ProcessingEnvironment) { - var count = 0 - - if (!packagePrivateList.isEmpty()) { - val typeBuilder = TypeSpec.classBuilder("${elementClassName?.simpleName()}_Helper") - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - - for (columnDefinition in packagePrivateList) { - var helperClassName = "${columnDefinition.element.getPackage()}.${columnDefinition.element.enclosingElement.toClassName()?.simpleName()}_Helper" - if (columnDefinition is ReferenceColumnDefinition) { - val tableDefinition: TableDefinition? = databaseDefinition.objectHolder?.tableDefinitionMap?.get(columnDefinition.referencedClassName as TypeName) - if (tableDefinition != null) { - helperClassName = "${tableDefinition.element.getPackage()}.${ClassName.get(tableDefinition.element as TypeElement).simpleName()}_Helper" - } - } - val className = ElementUtility.getClassName(helperClassName, manager) - - if (className != null && PackagePrivateScopeColumnAccessor.containsColumn(className, columnDefinition.columnName)) { - typeBuilder.apply { - val samePackage = ElementUtility.isInSamePackage(manager, columnDefinition.element, this@EntityDefinition.element) - val methodName = columnDefinition.columnName.capitalize() - - `public static final`(columnDefinition.elementTypeName!!, "get$methodName", - param(elementTypeName!!, ModelUtils.variable)) { - if (samePackage) { - `return`("${ModelUtils.variable}.${columnDefinition.elementName}") - } else { - `return`("\$T.get$methodName(${ModelUtils.variable})", className) - } - } - - `public static final`(TypeName.VOID, "set$methodName", - param(elementTypeName, ModelUtils.variable), - param(columnDefinition.elementTypeName, "var")) { - if (samePackage) { - statement("${ModelUtils.variable}.${columnDefinition.elementName} = var") - } else { - statement("\$T.set$methodName(${ModelUtils.variable}, var)", className) - } - } - } - - count++ - } else if (className == null) { - manager.logError(EntityDefinition::class, "Could not find classname for: $helperClassName") - } - } - - // only write class if we have referenced fields. - if (count > 0) { - val javaFileBuilder = JavaFile.builder(packageName, typeBuilder.build()) - javaFileBuilder.build().writeTo(processingEnvironment.filer) - } - } - } - - /** - * Do not support inheritance on package private fields without having ability to generate code for it in - * same package. - */ - internal fun checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage: Boolean, element: Element): Boolean { - if (isPackagePrivateNotInSamePackage && !manager.elementBelongsInTable(element)) { - manager.logError("Package private inheritance on non-table/querymodel/view " + - "is not supported without a @InheritedColumn annotation." + - " Make $element from ${element.enclosingElement} public or private.") - return true - } - return false - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/IndexGroupsDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/IndexGroupsDefinition.kt deleted file mode 100644 index 5974374f687e913489519c1ce6d71658a0a481fb..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/IndexGroupsDefinition.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.processor.definition - -import com.grosner.kpoet.S -import com.grosner.kpoet.`=` -import com.grosner.kpoet.field -import com.grosner.kpoet.final -import com.grosner.kpoet.public -import com.grosner.kpoet.static -import com.dbflow5.annotation.IndexGroup -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.squareup.javapoet.ParameterizedTypeName -import java.util.concurrent.atomic.AtomicInteger - -/** - * Description: - */ -class IndexGroupsDefinition(private val tableDefinition: TableDefinition, indexGroup: IndexGroup) { - - val indexName = indexGroup.name - val indexNumber = indexGroup.number - val isUnique = indexGroup.unique - - val columnDefinitionList: MutableList = arrayListOf() - - val fieldSpec - get() = field(ParameterizedTypeName.get(com.dbflow5.processor.ClassNames.INDEX_PROPERTY, tableDefinition.elementClassName), - "index_$indexName") { - addModifiers(public, static, final) - `=` { - add("new \$T<>(${indexName.S}, $isUnique, \$T.class", - com.dbflow5.processor.ClassNames.INDEX_PROPERTY, tableDefinition.elementTypeName) - - if (columnDefinitionList.isNotEmpty()) { - add(",") - } - val index = AtomicInteger(0) - columnDefinitionList.forEach { it.appendIndexInitializer(this, index) } - add(")") - } - }.build()!! - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/ManyToManyDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/ManyToManyDefinition.kt deleted file mode 100644 index f9dc9a5a1185ec66fa936458befbc0f3cb8d6e82..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/ManyToManyDefinition.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ManyToMany -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.lower -import com.dbflow5.processor.utils.toClassName -import com.dbflow5.processor.utils.toTypeElement -import com.grosner.kpoet.L -import com.grosner.kpoet.`@` -import com.grosner.kpoet.`fun` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.field -import com.grosner.kpoet.final -import com.grosner.kpoet.member -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.AnnotationSpec -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.TypeElement - -/** - * Description: Generates the Model class that is used in a many to many. - */ -class ManyToManyDefinition(element: TypeElement, processorManager: ProcessorManager, - manyToMany: ManyToMany) - : BaseDefinition(element, processorManager) { - - val databaseTypeName: TypeName? = element.extractTypeNameFromAnnotation
{ it.database } - - private val referencedTable: TypeName = manyToMany.extractTypeNameFromAnnotation { it.referencedTable } - private val generateAutoIncrement: Boolean = manyToMany.generateAutoIncrement - private val sameTableReferenced: Boolean = referencedTable == elementTypeName - private val generatedTableClassName = manyToMany.generatedTableClassName - private val saveForeignKeyModels: Boolean = manyToMany.saveForeignKeyModels - private val thisColumnName = manyToMany.thisTableColumnName - private val referencedColumnName = manyToMany.referencedTableColumnName - - init { - if (!thisColumnName.isNullOrEmpty() && !referencedColumnName.isNullOrEmpty() - && thisColumnName == referencedColumnName) { - manager.logError(ManyToManyDefinition::class, "The thisTableColumnName and referenceTableColumnName cannot be the same") - } - } - - fun prepareForWrite() { - if (generatedTableClassName.isNullOrEmpty()) { - val referencedOutput = referencedTable.toTypeElement(manager).toClassName(manager) - setOutputClassName("_${referencedOutput?.simpleName()}") - } else { - setOutputClassNameFull(generatedTableClassName) - } - } - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - typeBuilder.apply { - addAnnotation(AnnotationSpec.builder(Table::class.java) - .addMember("database", "\$T.class", databaseTypeName).build()) - - val referencedDefinition = manager.getTableDefinition(databaseTypeName, referencedTable) - val selfDefinition = manager.getTableDefinition(databaseTypeName, elementTypeName) - - if (generateAutoIncrement) { - addField(field(`@`(PrimaryKey::class) { this["autoincrement"] = "true" }, TypeName.LONG, "_id").build()) - - `fun`(TypeName.LONG, "getId") { - modifiers(public, final) - `return`("_id") - } - } - - referencedDefinition?.let { appendColumnDefinitions(this, it, 0, referencedColumnName) } - selfDefinition?.let { appendColumnDefinitions(this, it, 1, thisColumnName) } - } - } - - override val extendsClass: TypeName? = ClassNames.BASE_MODEL - - private fun appendColumnDefinitions(typeBuilder: TypeSpec.Builder, - referencedDefinition: TableDefinition, index: Int, optionalName: String) { - var fieldName = referencedDefinition.elementName.lower() - if (sameTableReferenced) { - fieldName += index.toString() - } - // override with the name (if specified) - if (!optionalName.isNullOrEmpty()) { - fieldName = optionalName - } - - typeBuilder.apply { - `field`(referencedDefinition.elementClassName!!, fieldName) { - if (!generateAutoIncrement) { - `@`(PrimaryKey::class) - } - `@`(ForeignKey::class) { member("saveForeignKeyModel", saveForeignKeyModels.toString()) } - } - `fun`(referencedDefinition.elementClassName!!, "get${fieldName.capitalize()}") { - modifiers(public, final) - `return`(fieldName.L) - } - `fun`(TypeName.VOID, "set${fieldName.capitalize()}", - param(referencedDefinition.elementClassName!!, "param")) { - modifiers(public, final) - statement("$fieldName = param") - } - } - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/Methods.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/Methods.kt deleted file mode 100644 index ae8a1444feafd5f00ec08901facf36bdea698d86..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/Methods.kt +++ /dev/null @@ -1,625 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.wrapperCommaIfBaseModel -import com.dbflow5.processor.utils.ModelUtils -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.codeBlock -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.quote -import com.grosner.kpoet.S -import com.grosner.kpoet.`=` -import com.grosner.kpoet.`for` -import com.grosner.kpoet.`private final field` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.code -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import com.squareup.javapoet.WildcardTypeName -import java.util.concurrent.atomic.AtomicInteger -import javax.lang.model.element.Modifier - -/** - * Description: - */ -interface MethodDefinition { - - val methodSpec: MethodSpec? -} - -/** - * Description: - * - * @author Andrew Grosner (fuzz) - */ -/** - * Description: Writes the bind to content values method in the ModelDAO. - */ -class BindToContentValuesMethod(private val entityDefinition: EntityDefinition, - private val isInsert: Boolean, - private val implementsContentValuesListener: Boolean) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - val methodBuilder = MethodSpec.methodBuilder(if (isInsert) "bindToInsertValues" else "bindToContentValues") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.CONTENT_VALUES, PARAM_CONTENT_VALUES) - .addParameter(entityDefinition.parameterClassName, ModelUtils.variable) - .returns(TypeName.VOID) - - var retMethodBuilder: MethodSpec.Builder? = methodBuilder - - if (isInsert) { - entityDefinition.columnDefinitions.forEach { - if (it.type !is ColumnDefinition.Type.PrimaryAutoIncrement - && it.type !is ColumnDefinition.Type.RowId) { - methodBuilder.addCode(it.contentValuesStatement) - } - } - - if (implementsContentValuesListener) { - methodBuilder.addStatement("\$L.onBindTo\$LValues(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) - } - } else { - if (entityDefinition.primaryKeyColumnBehavior.hasAutoIncrement - || entityDefinition.primaryKeyColumnBehavior.hasRowID) { - val autoIncrement = entityDefinition.primaryKeyColumnBehavior.associatedColumn - autoIncrement?.let { - methodBuilder.addCode(autoIncrement.contentValuesStatement) - } - } else if (!implementsContentValuesListener) { - retMethodBuilder = null - } - - methodBuilder.addStatement("bindToInsertValues(\$L, \$L)", PARAM_CONTENT_VALUES, ModelUtils.variable) - if (implementsContentValuesListener) { - methodBuilder.addStatement("\$L.onBindTo\$LValues(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) - } - } - - return retMethodBuilder?.build() - } - - companion object { - val PARAM_CONTENT_VALUES = "values" - } -} - -/** - * Description: - */ -class BindToStatementMethod(private val tableDefinition: TableDefinition, - private val mode: Mode) : MethodDefinition { - - enum class Mode { - INSERT { - override val methodName = "bindToInsertStatement" - - override val sqlListenerName = "onBindToInsertStatement" - }, - UPDATE { - override val methodName = "bindToUpdateStatement" - - override val sqlListenerName = "onBindToUpdateStatement" - }, - DELETE { - override val methodName = "bindToDeleteStatement" - - override val sqlListenerName = "onBindToDeleteStatement" - }; - - abstract val methodName: String - - abstract val sqlListenerName: String - } - - override val methodSpec: MethodSpec? - get() { - val methodBuilder = MethodSpec.methodBuilder(mode.methodName) - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.DATABASE_STATEMENT, PARAM_STATEMENT) - .addParameter(tableDefinition.parameterClassName, - ModelUtils.variable).returns(TypeName.VOID) - - // attach non rowid first, then go onto the WHERE clause - when (mode) { - Mode.INSERT -> { - val start = AtomicInteger(1) - tableDefinition.sqlColumnDefinitions - .forEach { - methodBuilder.addCode(it.getSQLiteStatementMethod(start)) - start.incrementAndGet() - } - } - Mode.UPDATE -> { - val realCount = AtomicInteger(1) - // attach non rowid first, then go onto the WHERE clause - tableDefinition.sqlColumnDefinitions - .forEach { - methodBuilder.addCode(it.getSQLiteStatementMethod(realCount)) - realCount.incrementAndGet() - } - tableDefinition.primaryColumnDefinitions.forEach { - methodBuilder.addCode(it.getSQLiteStatementMethod(realCount, - defineProperty = false)) - realCount.incrementAndGet() - } - } - Mode.DELETE -> { - val realCount = AtomicInteger(1) - tableDefinition.primaryColumnDefinitions.forEach { - methodBuilder.addCode(it.getSQLiteStatementMethod(realCount)) - realCount.incrementAndGet() - } - } - } - - if (tableDefinition.implementsSqlStatementListener) { - methodBuilder.addStatement("${ModelUtils.variable}.${mode.sqlListenerName}($PARAM_STATEMENT)") - } - - return methodBuilder.build() - } - - companion object { - - val PARAM_STATEMENT = "statement" - } -} - -/** - * Description: - */ -class CreationQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec - get() = `override fun`(String::class, "getCreationQuery") { - modifiers(public, final) - if (tableDefinition.type.isVirtual) { - addCode("return ${ - codeBlock { - add("CREATE VIRTUAL TABLE IF NOT EXISTS ${tableDefinition.associationalBehavior.name.quote()} USING ") - when (tableDefinition.type) { - TableDefinition.Type.FTS4 -> add("FTS4") - TableDefinition.Type.FTS3 -> add("FTS3") - else -> { - ProcessorManager.manager.logError("Invalid table type found ${tableDefinition.type}") - } - } - add("(") - // FTS4 uses column names directly. - add(tableDefinition.columnDefinitions.joinToString { it.columnName.quote() }) - tableDefinition.ftsBehavior?.addContentTableCode(tableDefinition.columnDefinitions.isNotEmpty(), this) - add(")") - }.S - };\n") - } else { - - val foreignSize = tableDefinition.foreignKeyDefinitions.size - - val creationBuilder = codeBlock { - add("CREATE ${if (tableDefinition.temporary) "TEMP " else ""}TABLE IF NOT EXISTS ${tableDefinition.associationalBehavior.name.quote()}(") - add(tableDefinition.columnDefinitions.joinToString { it.creationName.toString() }) - tableDefinition.uniqueGroupsDefinitions.forEach { - if (it.columnDefinitionList.isNotEmpty()) add(it.creationName) - } - - if (!tableDefinition.primaryKeyColumnBehavior.hasAutoIncrement) { - val primarySize = tableDefinition.primaryColumnDefinitions.size - if (primarySize > 0) { - add(", PRIMARY KEY(${tableDefinition.primaryColumnDefinitions.joinToString { it.primaryKeyName.toString() }})") - if (!tableDefinition.primaryKeyConflictActionName.isNullOrEmpty()) { - add(" ON CONFLICT ${tableDefinition.primaryKeyConflictActionName}") - } - } - } - if (foreignSize == 0) { - add(")") - } - this - } - - val codeBuilder = CodeBlock.builder() - .add("return \"$creationBuilder") - - tableDefinition.foreignKeyDefinitions.forEach { fk -> - val referencedTableDefinition = ProcessorManager.manager.getReferenceDefinition(tableDefinition.associationalBehavior.databaseTypeName, fk.referencedClassName) - if (referencedTableDefinition == null) { - fk.throwCannotFindReference() - } else { - codeBuilder.add(buildString { - append(", FOREIGN KEY(") - append(fk.referenceDefinitionList.joinToString { it.columnName.quote() }) - append(") REFERENCES ") - append("${referencedTableDefinition.associationalBehavior.name} ") - append("(") - append(fk.referenceDefinitionList.joinToString { it.foreignColumnName.quote() }) - append(") ON UPDATE ${fk.foreignKeyColumnBehavior!!.onUpdate.name.replace("_", " ")}") - append(" ON DELETE ${fk.foreignKeyColumnBehavior.onDelete.name.replace("_", " ")}") - if (fk.foreignKeyColumnBehavior.deferred) { - append(" DEFERRABLE INITIALLY DEFERRED") - } - }) - } - } - if (foreignSize > 0) { - codeBuilder.add(")") - } - codeBuilder.add("\";\n") - - addCode(codeBuilder.build()) - } - } -} - -/** - * Description: Writes out the custom type converter fields. - */ -class CustomTypeConverterPropertyMethod(private val entityDefinition: EntityDefinition) - : TypeAdder, CodeAdder { - - override fun addToType(typeBuilder: TypeSpec.Builder) { - val customTypeConverters = entityDefinition.associatedTypeConverters.keys - customTypeConverters.forEach { - typeBuilder.`private final field`(it, "typeConverter${it.simpleName()}") { `=`("new \$T()", it) } - } - - val globalTypeConverters = entityDefinition.globalTypeConverters.keys - globalTypeConverters.forEach { - typeBuilder.`private final field`(it, "global_typeConverter${it.simpleName()}") - } - } - - override fun addCode(code: CodeBlock.Builder): CodeBlock.Builder { - // Constructor code - val globalTypeConverters = entityDefinition.globalTypeConverters.keys - globalTypeConverters.forEach { - val def = entityDefinition.globalTypeConverters[it] - val firstDef = def?.get(0) - firstDef?.typeConverterElementNames?.forEach { elementName -> - code.statement("global_typeConverter${it.simpleName()} " + - "= (\$T) holder.getTypeConverterForClass(\$T.class)", it, elementName) - } - } - return code - } -} - -/** - * Description: - */ -class ExistenceMethod(private val tableDefinition: EntityDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - if (tableDefinition.primaryColumnDefinitions.isNotEmpty()) { - val primaryColumn = tableDefinition.primaryKeyColumnBehavior.associatedColumn - ?: tableDefinition.primaryColumnDefinitions[0] - if (primaryColumn.shouldWriteExistence()) { - return `override fun`(TypeName.BOOLEAN, "exists", - param(tableDefinition.parameterClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, "wrapper")) { - modifiers(public, final) - code { - primaryColumn.appendExistenceMethod(this) - this - } - } - } - } - return null - } -} - -/** - * Description: - */ -class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, - private val mode: Mode) : MethodDefinition { - - enum class Mode { - INSERT, - SAVE - } - - override val methodSpec: MethodSpec? - get() { - return `override fun`(String::class, - when (mode) { - Mode.INSERT -> "getInsertStatementQuery" - Mode.SAVE -> "getSaveStatementQuery" - }) { - modifiers(public, final) - `return`(codeBlock { - add("INSERT ") - if (mode != Mode.SAVE) { - if (!tableDefinition.insertConflictActionName.isEmpty()) { - add("OR ${tableDefinition.insertConflictActionName} ") - } - } else { - add("OR ${ConflictAction.REPLACE} ") - } - add("INTO ${tableDefinition.associationalBehavior.name.quote()}(") - - tableDefinition.sqlColumnDefinitions - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(",") - add(columnDefinition.insertStatementColumnName) - - } - - add(") VALUES (") - - tableDefinition.sqlColumnDefinitions - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(",") - add(columnDefinition.insertStatementValuesString) - } - - add(")") - }.S) - } - } -} - -class UpdateStatementQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - return `override fun`(String::class, "getUpdateStatementQuery") { - modifiers(public, final) - `return`(codeBlock { - add("UPDATE") - if (!tableDefinition.updateConflictActionName.isEmpty()) { - add(" OR ${tableDefinition.updateConflictActionName}") - } - add(" ${tableDefinition.associationalBehavior.name.quote()} SET ") - - // can only change non primary key values. - tableDefinition.sqlColumnDefinitions - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(",") - add(columnDefinition.updateStatementBlock) - - } - add(" WHERE ") - - // primary key values used as WHERE - tableDefinition.columnDefinitions - .filter { it.type.isPrimaryField || tableDefinition.type.isVirtual } - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(" AND ") - add(columnDefinition.updateStatementBlock) - } - this - }.S) - } - } -} - -class DeleteStatementQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - return `override fun`(String::class, "getDeleteStatementQuery") { - modifiers(public, final) - `return`(codeBlock { - add("DELETE FROM ${tableDefinition.associationalBehavior.name.quote()} WHERE ") - - // primary key values used as WHERE - tableDefinition.columnDefinitions - .filter { it.type.isPrimaryField || tableDefinition.type.isVirtual } - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(" AND ") - add(columnDefinition.updateStatementBlock) - } - this - }.S) - } - } -} - -/** - * Description: - */ -class LoadFromCursorMethod(private val entityDefinition: EntityDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec - get() = `override fun`(entityDefinition.parameterClassName!!, "loadFromCursor", - param(ClassNames.FLOW_CURSOR, PARAM_CURSOR), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { - modifiers(public, final) - statement("\$1T ${ModelUtils.variable} = new \$1T()", entityDefinition.parameterClassName) - val index = AtomicInteger(0) - val nameAllocator = NameAllocator() // unique names - entityDefinition.columnDefinitions.forEach { - addCode(it.getLoadFromCursorMethod(true, index, nameAllocator)) - index.incrementAndGet() - } - - if (entityDefinition is TableDefinition) { - code { - entityDefinition.oneToManyDefinitions - .filter { it.isLoad } - .forEach { it.writeLoad(this) } - this - } - } - - if (entityDefinition.implementsLoadFromCursorListener) { - statement("${ModelUtils.variable}.onLoadFromCursor($PARAM_CURSOR)") - } - `return`(ModelUtils.variable) - } - - - companion object { - - val PARAM_CURSOR = "cursor" - } -} - -/** - * Description: - */ -class OneToManyDeleteMethod(private val tableDefinition: TableDefinition, - private val isPlural: Boolean = false) : MethodDefinition { - - private val variableName = if (isPlural) "models" else ModelUtils.variable - private val typeName: TypeName = if (!isPlural) tableDefinition.elementClassName!! else - ParameterizedTypeName.get(ClassName.get(Collection::class.java), WildcardTypeName.subtypeOf(tableDefinition.elementClassName!!)) - - private val methodName = "delete${if (isPlural) "All" else ""}" - - override val methodSpec: MethodSpec? - get() { - val shouldWrite = tableDefinition.oneToManyDefinitions.any { it.isDelete } - if (shouldWrite || tableDefinition.cachingBehavior.cachingEnabled) { - val returnTypeName = if (isPlural) TypeName.LONG else TypeName.BOOLEAN - return `override fun`(returnTypeName, methodName, - param(typeName, variableName)) { - modifiers(public, final) - addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) - if (tableDefinition.cachingBehavior.cachingEnabled) { - statement("cacheAdapter.removeModel${if (isPlural) "s" else ""}FromCache(${variableName})") - } - - statement("\$T successful = super.${methodName}(${variableName}${wrapperCommaIfBaseModel(true)})", returnTypeName) - - if (isPlural && tableDefinition.oneToManyDefinitions.isNotEmpty()) { - `for`("\$T model: models", tableDefinition.elementClassName!!) { - tableDefinition.oneToManyDefinitions.forEach { it.writeDelete(this) } - this - } - } else { - tableDefinition.oneToManyDefinitions.forEach { it.writeDelete(this) } - } - - `return`("successful") - } - } - return null - } -} - -/** - * Description: Overrides the save, update, and insert methods if the [com.dbflow5.annotation.OneToMany.Method.SAVE] is used. - */ -class OneToManySaveMethod(private val tableDefinition: TableDefinition, - private val methodName: String, - private val isPlural: Boolean = false) : MethodDefinition { - - private val variableName = if (isPlural) "models" else ModelUtils.variable - private val typeName: TypeName = if (!isPlural) tableDefinition.elementClassName!! else - ParameterizedTypeName.get(ClassName.get(Collection::class.java), WildcardTypeName.subtypeOf(tableDefinition.elementClassName!!)) - - private val fullMethodName = "$methodName${if (isPlural) "All" else ""}" - - override val methodSpec: MethodSpec? - get() { - if (!tableDefinition.oneToManyDefinitions.isEmpty() || tableDefinition.cachingBehavior.cachingEnabled) { - var retType = TypeName.BOOLEAN - var retStatement = "successful" - if (isPlural) { - retType = ClassName.LONG - retStatement = "count" - } else if (fullMethodName == METHOD_INSERT) { - retType = ClassName.LONG - retStatement = "rowId" - } - - return `override fun`( - retType, fullMethodName, - param(typeName, variableName), - ) { - modifiers(public, final) - addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) - code { - if (isPlural) { - add("long count = ") - } else if (fullMethodName == METHOD_INSERT) { - add("long rowId = ") - } else if (fullMethodName == METHOD_UPDATE || fullMethodName == METHOD_SAVE) { - add("boolean successful = ") - } - statement("super.$fullMethodName(${variableName}${wrapperCommaIfBaseModel(true)})") - - if (tableDefinition.cachingBehavior.cachingEnabled) { - statement("cacheAdapter.storeModel${if (isPlural) "s" else ""}InCache(${variableName})") - } - this - } - - val filteredDefinitions = tableDefinition.oneToManyDefinitions.filter { it.isSave } - - fun saveDefinitions() { - filteredDefinitions.forEach { oneToManyDefinition -> - when (methodName) { - METHOD_SAVE -> oneToManyDefinition.writeSave(this) - METHOD_UPDATE -> oneToManyDefinition.writeUpdate(this) - METHOD_INSERT -> oneToManyDefinition.writeInsert(this) - } - } - } - if (isPlural && filteredDefinitions.isNotEmpty()) { - `for`("\$T model: models", tableDefinition.elementClassName!!) { - saveDefinitions() - this - } - } else { - saveDefinitions() - } - - `return`(retStatement) - } - } else { - return null - } - } - - companion object { - val METHOD_SAVE = "save" - val METHOD_UPDATE = "update" - val METHOD_INSERT = "insert" - } -} - -/** - * Description: Creates a method that builds a clause of ConditionGroup that represents its primary keys. Useful - * for updates or deletes. - */ -class PrimaryConditionMethod(private val tableDefinition: EntityDefinition) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() = `override fun`(ClassNames.OPERATOR_GROUP, "getPrimaryConditionClause", - param(tableDefinition.parameterClassName!!, ModelUtils.variable)) { - modifiers(public, final) - code { - statement("\$T clause = \$T.clause()", ClassNames.OPERATOR_GROUP, ClassNames.OPERATOR_GROUP) - tableDefinition.primaryColumnDefinitions.forEach { - val codeBuilder = CodeBlock.builder() - it.appendPropertyComparisonAccessStatement(codeBuilder) - add(codeBuilder.build()) - } - this - } - `return`("clause") - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/MigrationDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/MigrationDefinition.kt deleted file mode 100644 index ad1821ad2f1735a55b47e07182a641387f72ee4c..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/MigrationDefinition.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.Migration -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.dbflow5.processor.utils.isNullOrEmpty -import com.grosner.kpoet.typeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.TypeElement - -/** - * Description: Used in holding data about migration files. - */ -class MigrationDefinition(migration: Migration, - processorManager: ProcessorManager, typeElement: TypeElement) - : BaseDefinition(typeElement, processorManager) { - - val databaseName: TypeName? - val version: Int - val priority: Int - - var constructorName: String? = null - private set - - init { - setOutputClassName("") - databaseName = migration.extractTypeNameFromAnnotation { it.database } - version = migration.version - priority = migration.priority - - val elements = typeElement.enclosedElements - elements.forEach { element -> - if (element is ExecutableElement && element.simpleName.toString() == "") { - if (!constructorName.isNullOrEmpty()) { - manager.logError(MigrationDefinition::class, "Migrations cannot have more than one constructor. " + - "They can only have an Empty() or single-parameter constructor Empty(Empty.class) that specifies " + - "the .class of this migration class.") - } - - if (element.parameters.isEmpty()) { - constructorName = "()" - } else if (element.parameters.size == 1) { - val params = element.parameters - val param = params[0] - - val type = param.asType().typeName - if (type is ParameterizedTypeName && type.rawType == ClassName.get(Class::class.java)) { - val containedType = type.typeArguments[0] - constructorName = CodeBlock.of("(\$T.class)", containedType).toString() - } else { - manager.logError(MigrationDefinition::class, "Wrong parameter type found for $typeElement. Found $type but required ModelClass.class") - } - } - } - } - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/ModelViewDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/ModelViewDefinition.kt deleted file mode 100644 index 3c0c7a433d14ce5b9d5c5e366feafc7310f5c816..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/ModelViewDefinition.kt +++ /dev/null @@ -1,165 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.ModelView -import com.dbflow5.annotation.ModelViewQuery -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ColumnValidator -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.behavior.AssociationalBehavior -import com.dbflow5.processor.definition.behavior.CreationQueryBehavior -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.ensureVisibleStatic -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.dbflow5.processor.utils.implementsClass -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.simpleString -import com.dbflow5.processor.utils.toTypeElement -import com.dbflow5.processor.utils.toTypeErasedElement -import com.dbflow5.quoteIfNeeded -import com.grosner.kpoet.S -import com.grosner.kpoet.`=` -import com.grosner.kpoet.`public static final field` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.public -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.TypeElement - -/** - * Description: Used in writing ModelViewAdapters - */ -class ModelViewDefinition(modelView: ModelView, - manager: ProcessorManager, - element: TypeElement) - : EntityDefinition(element, manager) { - - private var queryFieldName: String? = null - - override val methods: Array = arrayOf( - LoadFromCursorMethod(this), - ExistenceMethod(this), - PrimaryConditionMethod(this)) - - private val creationQueryBehavior = CreationQueryBehavior(createWithDatabase = modelView.createWithDatabase) - - val priority = modelView.priority - - init { - setOutputClassName("_ViewTable") - } - - override val associationalBehavior = AssociationalBehavior( - name = if (modelView.name.isNullOrEmpty()) modelClassName else modelView.name, - databaseTypeName = modelView.extractTypeNameFromAnnotation { it.database }, - allFields = modelView.allFields - ) - - override val cursorHandlingBehavior = CursorHandlingBehavior( - orderedCursorLookup = modelView.orderedCursorLookUp, - assignDefaultValuesFromCursor = modelView.assignDefaultValuesFromCursor) - - override fun prepareForWriteInternal() { - queryFieldName = null - typeElement?.let { createColumnDefinitions(it) } - } - - override fun createColumnDefinitions(typeElement: TypeElement) { - val columnGenerator = BasicColumnGenerator(manager) - val variableElements = ElementUtility.getAllElements(typeElement, manager) - for (element in variableElements) { - classElementLookUpMap[element.simpleName.toString()] = element - } - - val columnValidator = ColumnValidator() - for (variableElement in variableElements) { - - val isValidAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement) - val isColumnMap = variableElement.annotation() != null - - if (variableElement.annotation() != null || isValidAllFields - || isColumnMap) { - - // package private, will generate helper - val isPackagePrivate = ElementUtility.isPackagePrivate(variableElement) - columnGenerator.generate(variableElement, this)?.let { columnDefinition -> - if (columnValidator.validate(manager, columnDefinition)) { - columnDefinitions.add(columnDefinition) - if (isPackagePrivate) { - packagePrivateList.add(columnDefinition) - } - } - - if (columnDefinition.type.isPrimaryField) { - manager.logError("ModelView $elementName cannot have primary keys") - } - } - } else if (variableElement.annotation() != null) { - if (!queryFieldName.isNullOrEmpty()) { - manager.logError("Found duplicate queryField name: $queryFieldName for $elementClassName") - } - - val element = variableElement.toTypeErasedElement() as? ExecutableElement - if (element != null) { - val returnElement = element.returnType.toTypeElement() - ensureVisibleStatic(element, typeElement, "ModelViewQuery") - if (!returnElement.implementsClass(manager.processingEnvironment, com.dbflow5.processor.ClassNames.QUERY)) { - manager.logError("The function ${variableElement.simpleName} must return ${com.dbflow5.processor.ClassNames.QUERY} from $elementName") - } - } - - queryFieldName = variableElement.simpleString - } - } - - if (queryFieldName.isNullOrEmpty()) { - manager.logError("$elementClassName is missing the @ModelViewQuery field.") - } - } - - override val primaryColumnDefinitions: List - get() = columnDefinitions - - override val extendsClass: TypeName? - get() = ParameterizedTypeName.get(com.dbflow5.processor.ClassNames.MODEL_VIEW_ADAPTER, elementClassName) - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - typeBuilder.apply { - `public static final field`(String::class, "VIEW_NAME") { `=`(associationalBehavior.name.S) } - - elementClassName?.let { elementClassName -> - columnDefinitions.forEach { it.addPropertyDefinition(typeBuilder, elementClassName) } - } - - this.writeConstructor() - - writeGetModelClass(typeBuilder, elementClassName) - - creationQueryBehavior.addToType(this) - - `override fun`(String::class, "getCreationQuery") { - modifiers(public, final) - `return`("\"CREATE VIEW IF NOT EXISTS ${associationalBehavior.name.quoteIfNeeded()} AS \" + \$T.\$L().getQuery()", elementClassName, queryFieldName) - } - associationalBehavior.writeName(typeBuilder) - - `override fun`(ClassNames.OBJECT_TYPE, "getType") { - modifiers(public, final) - `return`("\$T.View", ClassNames.OBJECT_TYPE) - } - } - - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - } - -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/OneToManyDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/OneToManyDefinition.kt deleted file mode 100644 index d78290a61412598f279166c8a12352ea281d27a8..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/OneToManyDefinition.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.OneToMany -import com.dbflow5.annotation.OneToManyMethod -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.column.ColumnAccessor -import com.dbflow5.processor.definition.column.GetterSetter -import com.dbflow5.processor.definition.column.PrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.VisibleScopeColumnAccessor -import com.dbflow5.processor.definition.column.modelBlock -import com.dbflow5.processor.definition.column.wrapperCommaIfBaseModel -import com.dbflow5.processor.definition.column.wrapperIfBaseModel -import com.dbflow5.processor.utils.ModelUtils -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.isSubclass -import com.dbflow5.processor.utils.simpleString -import com.dbflow5.processor.utils.statement -import com.dbflow5.processor.utils.toTypeElement -import com.grosner.kpoet.`for` -import com.grosner.kpoet.`if` -import com.grosner.kpoet.end -import com.grosner.kpoet.statement -import com.grosner.kpoet.typeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.WildcardTypeName -import javax.lang.model.element.Element -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement - -/** - * Description: Represents the [OneToMany] annotation. - */ -class OneToManyDefinition(executableElement: ExecutableElement, - processorManager: ProcessorManager, - parentElements: Collection) - : BaseDefinition(executableElement, processorManager) { - - private var _methodName: String - - var variableName: String - - var methods = mutableListOf() - - val isLoad - get() = isAll || methods.contains(OneToManyMethod.LOAD) - - val isAll - get() = methods.contains(OneToManyMethod.ALL) - - val isDelete: Boolean - get() = isAll || methods.contains(OneToManyMethod.DELETE) - - val isSave: Boolean - get() = isAll || methods.contains(OneToManyMethod.SAVE) - - var referencedTableType: TypeName? = null - var hasWrapper = false - - private var columnAccessor: ColumnAccessor - private var extendsModel = false - private var referencedType: TypeElement? = null - - private var efficientCodeMethods = false - - init { - - val oneToMany = executableElement.annotation()!! - - efficientCodeMethods = oneToMany.efficientMethods - - _methodName = executableElement.simpleName.toString() - variableName = oneToMany.variableName - if (variableName.isEmpty()) { - variableName = _methodName.replaceFirst("get", "") - variableName = variableName.substring(0, 1).toLowerCase() + variableName.substring(1) - } - - val privateAccessor = PrivateScopeColumnAccessor(variableName, object : GetterSetter { - override val getterName: String = "" - override val setterName: String = "set${variableName.capitalize()}" - }, optionalGetterParam = if (hasWrapper) ModelUtils.wrapper else "") - - var isVariablePrivate = false - val referencedElement = parentElements.firstOrNull { it.simpleString == variableName } - if (referencedElement == null) { - // check on setter. if setter exists, we can reference it safely since a getter has already been defined. - if (!parentElements.any { it.simpleString == privateAccessor.setterNameElement }) { - manager.logError(OneToManyDefinition::class, - "@OneToMany definition $elementName " + - "Cannot find setter ${privateAccessor.setterNameElement} " + - "for variable $variableName.") - } else { - isVariablePrivate = true - } - } else { - isVariablePrivate = referencedElement.modifiers.contains(Modifier.PRIVATE) - } - - methods.addAll(oneToMany.oneToManyMethods) - - val parameters = executableElement.parameters - if (parameters.isNotEmpty()) { - if (parameters.size > 1) { - manager.logError(OneToManyDefinition::class, "OneToMany Methods can only have one parameter and that be the DatabaseWrapper.") - } else { - val param = parameters[0] - val name = param.asType().typeName - if (name == com.dbflow5.processor.ClassNames.DATABASE_WRAPPER) { - hasWrapper = true - } else { - manager.logError(OneToManyDefinition::class, "OneToMany Methods can only specify a ${com.dbflow5.processor.ClassNames.DATABASE_WRAPPER} as its parameter.") - } - } - } - - columnAccessor = if (isVariablePrivate) privateAccessor else VisibleScopeColumnAccessor(variableName) - - val returnType = executableElement.returnType - val typeName = TypeName.get(returnType) - if (typeName is ParameterizedTypeName) { - val typeArguments = typeName.typeArguments - if (typeArguments.size == 1) { - var refTableType = typeArguments[0] - if (refTableType is WildcardTypeName) { - refTableType = refTableType.upperBounds[0] - } - referencedTableType = refTableType - - referencedType = referencedTableType.toTypeElement(manager) - extendsModel = referencedType.isSubclass(manager.processingEnvironment, com.dbflow5.processor.ClassNames.MODEL) - } - } - - } - - private val methodName = "${ModelUtils.variable}.$_methodName(${wrapperIfBaseModel(hasWrapper)})" - - /** - * Writes the method to the specified builder for loading from DB. - */ - fun writeLoad(codeBuilder: CodeBlock.Builder) { - if (isLoad) { - codeBuilder.addStatement(methodName) - } - } - - /** - * Writes a delete method that will delete all related objects. - */ - fun writeDelete(method: MethodSpec.Builder) { - if (isDelete) { - writeLoopWithMethod(method, "delete") - method.statement(columnAccessor.set(CodeBlock.of("null"), modelBlock)) - } - } - - fun writeSave(codeBuilder: MethodSpec.Builder) { - if (isSave) writeLoopWithMethod(codeBuilder, "save") - } - - fun writeUpdate(codeBuilder: MethodSpec.Builder) { - if (isSave) writeLoopWithMethod(codeBuilder, "update") - } - - fun writeInsert(codeBuilder: MethodSpec.Builder) { - if (isSave) writeLoopWithMethod(codeBuilder, "insert") - } - - private fun writeLoopWithMethod(codeBuilder: MethodSpec.Builder, methodName: String) { - val oneToManyMethodName = this@OneToManyDefinition.methodName - codeBuilder.apply { - `if`("$oneToManyMethodName != null") { - // need to load adapter for non-model classes - if (!extendsModel || efficientCodeMethods) { - statement("\$T adapter = \$T.getModelAdapter(\$T.class)", - ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, referencedTableType), - ClassNames.FLOW_MANAGER, referencedTableType) - } - - if (efficientCodeMethods) { - statement("adapter.${methodName}All($oneToManyMethodName${wrapperCommaIfBaseModel(true)})") - } else { - `for`("\$T value: $oneToManyMethodName", ClassName.get(referencedType)) { - if (!extendsModel) { - statement("adapter.$methodName(value${wrapperCommaIfBaseModel(true)})") - } else { - statement("value.$methodName(${wrapperIfBaseModel(true)})") - } - this - } - } - } - }.end() - } -} - diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/QueryModelDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/QueryModelDefinition.kt deleted file mode 100644 index e7b1369f61a3cc817898310285841a8385806e0f..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/QueryModelDefinition.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.QueryModel -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ColumnValidator -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.behavior.AssociationalBehavior -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.TypeElement - -/** - * Description: - */ -class QueryModelDefinition(override val associationalBehavior: AssociationalBehavior, - override val cursorHandlingBehavior: CursorHandlingBehavior, - typeElement: TypeElement, - processorManager: ProcessorManager) - : EntityDefinition(typeElement, processorManager) { - - override val methods: Array = arrayOf(LoadFromCursorMethod(this), - ExistenceMethod(this), - PrimaryConditionMethod(this)) - - constructor(queryModel: QueryModel, typeElement: TypeElement, - processorManager: ProcessorManager) : this( - AssociationalBehavior( - name = typeElement.simpleName.toString(), - databaseTypeName = queryModel.extractTypeNameFromAnnotation { it.database }, - allFields = queryModel.allFields - ), - CursorHandlingBehavior( - orderedCursorLookup = queryModel.orderedCursorLookUp, - assignDefaultValuesFromCursor = queryModel.assignDefaultValuesFromCursor - ), - typeElement, processorManager) - - /** - * [ColumnMap] constructor. - */ - constructor(typeElement: TypeElement, - databaseTypeName: TypeName, - processorManager: ProcessorManager) : this( - AssociationalBehavior( - name = typeElement.simpleName.toString(), - databaseTypeName = databaseTypeName, - allFields = true - ), - CursorHandlingBehavior(), - typeElement, processorManager) - - init { - setOutputClassName("_QueryTable") - processorManager.addModelToDatabase(elementClassName, associationalBehavior.databaseTypeName) - } - - override fun prepareForWriteInternal() { - typeElement?.let { createColumnDefinitions(it) } - } - - override val extendsClass: TypeName? - get() = ParameterizedTypeName.get(ClassNames.RETRIEVAL_ADAPTER, elementClassName) - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - typeBuilder.apply { - elementClassName?.let { elementClassName -> - columnDefinitions.forEach { it.addPropertyDefinition(this, elementClassName) } - } - - writeGetModelClass(typeBuilder, elementClassName) - this.writeConstructor() - } - - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - } - - override fun createColumnDefinitions(typeElement: TypeElement) { - val columnGenerator = BasicColumnGenerator(manager) - val variableElements = ElementUtility.getAllElements(typeElement, manager) - for (element in variableElements) { - classElementLookUpMap[element.simpleName.toString()] = element - } - - val columnValidator = ColumnValidator() - for (variableElement in variableElements) { - - // no private static or final fields - val isAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement) - // package private, will generate helper - val isColumnMap = variableElement.annotation() != null - - if (variableElement.annotation() != null || isAllFields || isColumnMap) { - val isPackagePrivate = ElementUtility.isPackagePrivate(element) - columnGenerator.generate(variableElement, this)?.let { columnDefinition -> - if (columnValidator.validate(manager, columnDefinition)) { - columnDefinitions.add(columnDefinition) - if (isPackagePrivate) { - packagePrivateList.add(columnDefinition) - } - } - - if (columnDefinition.type.isPrimaryField) { - manager.logError("QueryModel $elementName cannot have primary keys") - } - } - } - } - } - - override val primaryColumnDefinitions: List - get() = columnDefinitions - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/TableDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/TableDefinition.kt deleted file mode 100644 index f0446292f23829e756f14d302b08b91fdd298675..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/TableDefinition.kt +++ /dev/null @@ -1,668 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.Fts3 -import com.dbflow5.annotation.Fts4 -import com.dbflow5.annotation.InheritedColumn -import com.dbflow5.annotation.InheritedPrimaryKey -import com.dbflow5.annotation.OneToMany -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.isNotNullOrEmpty -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ColumnValidator -import com.dbflow5.processor.OneToManyValidator -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BindToStatementMethod.Mode.DELETE -import com.dbflow5.processor.definition.BindToStatementMethod.Mode.INSERT -import com.dbflow5.processor.definition.BindToStatementMethod.Mode.UPDATE -import com.dbflow5.processor.definition.behavior.AssociationalBehavior -import com.dbflow5.processor.definition.behavior.CachingBehavior -import com.dbflow5.processor.definition.behavior.CreationQueryBehavior -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.definition.behavior.FTS3Behavior -import com.dbflow5.processor.definition.behavior.FTS4Behavior -import com.dbflow5.processor.definition.behavior.FtsBehavior -import com.dbflow5.processor.definition.behavior.PrimaryKeyColumnBehavior -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.DefinitionUtils -import com.dbflow5.processor.definition.column.ReferenceColumnDefinition -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.ModelUtils -import com.dbflow5.processor.utils.ModelUtils.wrapper -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.dbflow5.processor.utils.implementsClass -import com.dbflow5.processor.utils.isNullOrEmpty -import com.grosner.kpoet.L -import com.grosner.kpoet.S -import com.grosner.kpoet.`=` -import com.grosner.kpoet.`public static final field` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.`throw new` -import com.grosner.kpoet.code -import com.grosner.kpoet.default -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.protected -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.grosner.kpoet.switch -import com.squareup.javapoet.ArrayTypeName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import java.util.concurrent.atomic.AtomicInteger -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement - -/** - * Description: Used in writing ModelAdapters - */ -class TableDefinition(private val table: Table, - manager: ProcessorManager, element: TypeElement) - : EntityDefinition(element, manager) { - - enum class Type { - Normal, - FTS3, - FTS4; - - val isVirtual: Boolean - get() = when (this) { - Normal -> false - else -> true - } - } - - var insertConflictActionName: String = "" - - var updateConflictActionName: String = "" - - var primaryKeyConflictActionName: String = "" - - val _primaryColumnDefinitions = mutableListOf() - val foreignKeyDefinitions = mutableListOf() - val columnMapDefinitions = mutableListOf() - val uniqueGroupsDefinitions = mutableListOf() - val indexGroupsDefinitions = mutableListOf() - - var implementsContentValuesListener = false - - var implementsSqlStatementListener = false - - override val methods: Array = arrayOf( - BindToStatementMethod(this, INSERT), - BindToStatementMethod(this, UPDATE), - BindToStatementMethod(this, DELETE), - InsertStatementQueryMethod(this, InsertStatementQueryMethod.Mode.INSERT), - InsertStatementQueryMethod(this, InsertStatementQueryMethod.Mode.SAVE), - UpdateStatementQueryMethod(this), - DeleteStatementQueryMethod(this), - CreationQueryMethod(this), - LoadFromCursorMethod(this), - ExistenceMethod(this), - PrimaryConditionMethod(this), - OneToManyDeleteMethod(this), - OneToManyDeleteMethod(this, isPlural = true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE, isPlural = true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT, isPlural = true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE, isPlural = true), - ) - - private val contentValueMethods: Array - - private val creationQueryBehavior = CreationQueryBehavior(createWithDatabase = table.createWithDatabase) - val useIsForPrivateBooleans: Boolean = table.useBooleanGetterSetters - private val generateContentValues: Boolean = table.generateContentValues - - val oneToManyDefinitions = mutableListOf() - - private val columnMap = mutableMapOf() - private val columnUniqueMap = mutableMapOf>() - private val inheritedColumnMap = hashMapOf() - private val inheritedFieldNameList = mutableListOf() - private val inheritedPrimaryKeyMap = hashMapOf() - - var hasPrimaryConstructor = false - - override val associationalBehavior = AssociationalBehavior( - name = if (table.name.isNullOrEmpty()) element.simpleName.toString() else table.name, - databaseTypeName = table.extractTypeNameFromAnnotation { it.database }, - allFields = table.allFields) - - override val cursorHandlingBehavior = CursorHandlingBehavior( - orderedCursorLookup = table.orderedCursorLookUp, - assignDefaultValuesFromCursor = table.assignDefaultValuesFromCursor) - - val cachingBehavior = CachingBehavior( - cachingEnabled = table.cachingEnabled, - customCacheSize = table.cacheSize, - customCacheFieldName = null, - customMultiCacheFieldName = null) - - val type: Type - - val ftsBehavior: FtsBehavior? - - val temporary: Boolean - - init { - setOutputClassName("_Table") - - manager.addModelToDatabase(elementClassName, associationalBehavior.databaseTypeName) - - val fts4 = element.annotation() - val fts3 = element.annotation() - - if (fts3 != null && fts4 != null) { - manager.logError("Table $elementClassName cannot have multiple FTS annotations.") - } - - type = when { - fts4 != null -> Type.FTS4 - fts3 != null -> Type.FTS3 - else -> Type.Normal - } - - ftsBehavior = when (type) { - Type.FTS4 -> FTS4Behavior(contentTable = fts4!!.extractTypeNameFromAnnotation { it.contentTable }, - databaseTypeName = associationalBehavior.databaseTypeName, - elementName = elementName, - manager = manager) - Type.FTS3 -> FTS3Behavior(elementName, manager) - Type.Normal -> null - } - - val inheritedColumns = table.inheritedColumns - inheritedColumns.forEach { - if (inheritedFieldNameList.contains(it.fieldName)) { - manager.logError("A duplicate inherited column with name ${it.fieldName} " + - "was found for ${associationalBehavior.name}") - } - inheritedFieldNameList.add(it.fieldName) - inheritedColumnMap[it.fieldName] = it - } - - val inheritedPrimaryKeys = table.inheritedPrimaryKeys - inheritedPrimaryKeys.forEach { - if (inheritedFieldNameList.contains(it.fieldName)) { - manager.logError("A duplicate inherited column with name ${it.fieldName} " + - "was found for ${associationalBehavior.name}") - } - inheritedFieldNameList.add(it.fieldName) - inheritedPrimaryKeyMap[it.fieldName] = it - } - - implementsContentValuesListener = element.implementsClass(manager.processingEnvironment, - ClassNames.CONTENT_VALUES_LISTENER) - - implementsSqlStatementListener = element.implementsClass(manager.processingEnvironment, - ClassNames.SQLITE_STATEMENT_LISTENER) - - contentValueMethods = arrayOf(BindToContentValuesMethod(this, true, implementsContentValuesListener), - BindToContentValuesMethod(this, false, implementsContentValuesListener)) - - temporary = table.temporary - - } - - override fun prepareForWriteInternal() { - columnMap.clear() - _primaryColumnDefinitions.clear() - uniqueGroupsDefinitions.clear() - indexGroupsDefinitions.clear() - foreignKeyDefinitions.clear() - columnMapDefinitions.clear() - columnUniqueMap.clear() - oneToManyDefinitions.clear() - cachingBehavior.clear() - - // globular default - var insertConflict = table.insertConflict - if (insertConflict == ConflictAction.NONE && databaseDefinition.insertConflict != ConflictAction.NONE) { - insertConflict = databaseDefinition.insertConflict - } - - var updateConflict = table.updateConflict - if (updateConflict == ConflictAction.NONE && databaseDefinition.updateConflict != ConflictAction.NONE) { - updateConflict = databaseDefinition.updateConflict - } - - val primaryKeyConflict = table.primaryKeyConflict - - insertConflictActionName = if (insertConflict == ConflictAction.NONE) "" else insertConflict.name - updateConflictActionName = if (updateConflict == ConflictAction.NONE) "" else updateConflict.name - primaryKeyConflictActionName = if (primaryKeyConflict == ConflictAction.NONE) "" else primaryKeyConflict.name - - typeElement?.let { createColumnDefinitions(it) } - - val groups = table.uniqueColumnGroups - var uniqueNumbersSet: MutableSet = hashSetOf() - for (uniqueGroup in groups) { - if (uniqueNumbersSet.contains(uniqueGroup.groupNumber)) { - manager.logError("A duplicate unique group with number" + - " ${uniqueGroup.groupNumber} was found for ${associationalBehavior.name}") - } - val definition = UniqueGroupsDefinition(uniqueGroup) - columnDefinitions.filter { it.uniqueGroups.contains(definition.number) } - .forEach { definition.addColumnDefinition(it) } - uniqueGroupsDefinitions.add(definition) - uniqueNumbersSet.add(uniqueGroup.groupNumber) - } - - val indexGroups = table.indexGroups - uniqueNumbersSet = hashSetOf() - for (indexGroup in indexGroups) { - if (uniqueNumbersSet.contains(indexGroup.number)) { - manager.logError(TableDefinition::class, "A duplicate unique index number" + - " ${indexGroup.number} was found for $elementName") - } - val definition = IndexGroupsDefinition(this, indexGroup) - columnDefinitions.filter { it.indexGroups.contains(definition.indexNumber) } - .forEach { definition.columnDefinitionList.add(it) } - indexGroupsDefinitions.add(definition) - uniqueNumbersSet.add(indexGroup.number) - } - } - - override fun createColumnDefinitions(typeElement: TypeElement) { - val elements = ElementUtility.getAllElements(typeElement, manager) - - for (element in elements) { - classElementLookUpMap[element.simpleName.toString()] = element - if (element is ExecutableElement && element.parameters.isEmpty() - && element.simpleName.toString() == "" - && element.enclosingElement == typeElement - && !element.modifiers.contains(Modifier.PRIVATE)) { - hasPrimaryConstructor = true - } - } - - if (!hasPrimaryConstructor) { - manager.logError("For now, tables must have a visible, default, parameterless constructor. In" + - " Kotlin all field parameters must have default values.") - } - - val columnValidator = ColumnValidator() - val oneToManyValidator = OneToManyValidator() - elements.forEach { variableElement -> - // no private static or final fields for all columns, or any inherited columns here. - val isAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement) - - // package private, will generate helper - val isPackagePrivate = ElementUtility.isPackagePrivate(variableElement) - val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, variableElement, this.element) - - val isForeign = variableElement.annotation() != null - val isPrimary = variableElement.annotation() != null - val isInherited = inheritedColumnMap.containsKey(variableElement.simpleName.toString()) - val isInheritedPrimaryKey = inheritedPrimaryKeyMap.containsKey(variableElement.simpleName.toString()) - val isColumnMap = variableElement.annotation() != null - if (variableElement.annotation() != null || isForeign || isPrimary - || isAllFields || isInherited || isInheritedPrimaryKey || isColumnMap) { - - if (checkInheritancePackagePrivate(isPackagePrivateNotInSamePackage, variableElement)) return - - val columnDefinition = if (isInheritedPrimaryKey) { - val inherited = inheritedPrimaryKeyMap[variableElement.simpleName.toString()] - ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage, - inherited?.column, inherited?.primaryKey) - } else if (isInherited) { - val inherited = inheritedColumnMap[variableElement.simpleName.toString()] - ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage, - inherited?.column, null, inherited?.nonNullConflict - ?: ConflictAction.NONE) - } else if (isForeign) { - ReferenceColumnDefinition(variableElement.annotation()!!, manager, this, - variableElement, isPackagePrivateNotInSamePackage) - } else if (isColumnMap) { - ReferenceColumnDefinition(variableElement.annotation()!!, - manager, this, variableElement, isPackagePrivateNotInSamePackage) - } else { - ColumnDefinition(manager, variableElement, - this, isPackagePrivateNotInSamePackage) - } - - if (columnValidator.validate(manager, columnDefinition)) { - columnDefinitions.add(columnDefinition) - if (isPackagePrivate) { - packagePrivateList.add(columnDefinition) - } - columnMap[columnDefinition.columnName] = columnDefinition - // check to ensure not null. - when { - columnDefinition.type is ColumnDefinition.Type.Primary -> - _primaryColumnDefinitions.add(columnDefinition) - columnDefinition.type is ColumnDefinition.Type.PrimaryAutoIncrement -> { - this.primaryKeyColumnBehavior = PrimaryKeyColumnBehavior( - hasRowID = false, - hasAutoIncrement = true, - associatedColumn = columnDefinition - ) - } - columnDefinition.type is ColumnDefinition.Type.RowId -> { - this.primaryKeyColumnBehavior = PrimaryKeyColumnBehavior( - hasRowID = true, - hasAutoIncrement = false, - associatedColumn = columnDefinition - ) - } - } - - primaryKeyColumnBehavior.associatedColumn?.let { associatedColumn -> - // check to ensure not null. - if (associatedColumn.isNullableType) { - manager.logWarning("Attempting to use nullable field type on an autoincrementing column. " + - "To suppress or remove this warning " + - "switch to java primitive, add @android.support.annotation.NonNull," + - "@org.jetbrains.annotation.NotNull, or in Kotlin don't make it nullable. Check the column ${associatedColumn.columnName} " + - "on ${associationalBehavior.name}") - } - } - - ftsBehavior?.validateColumnDefinition(columnDefinition) - - if (columnDefinition is ReferenceColumnDefinition) { - if (!columnDefinition.isColumnMap) { - foreignKeyDefinitions.add(columnDefinition) - } else { - columnMapDefinitions.add(columnDefinition) - } - } - - if (!columnDefinition.uniqueGroups.isEmpty()) { - for (group in columnDefinition.uniqueGroups) { - columnUniqueMap.getOrPut(group) { mutableSetOf() } - .add(columnDefinition) - } - } - } - } else if (variableElement.annotation() != null) { - val oneToManyDefinition = OneToManyDefinition(variableElement as ExecutableElement, manager, elements) - if (oneToManyValidator.validate(manager, oneToManyDefinition)) { - oneToManyDefinitions.add(oneToManyDefinition) - } - } else { - cachingBehavior.evaluateElement(variableElement, typeElement, manager) - } - } - - // ignore any referenced one to many field definitions from all fields. - columnDefinitions = columnDefinitions.filterTo(mutableListOf()) { column -> - oneToManyDefinitions.isEmpty() || oneToManyDefinitions.all { it.variableName != column.elementName } - } - } - - override val primaryColumnDefinitions: List - get() = primaryKeyColumnBehavior.associatedColumn?.let { arrayListOf(it) } - ?: when { - // fts4 use all columns, since there's no primary keys here. - ftsBehavior != null -> columnDefinitions - else -> _primaryColumnDefinitions - } - - override val extendsClass: TypeName? - get() = ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, elementClassName) - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - // check references to properly set them up. - foreignKeyDefinitions.forEach { it.checkNeedsReferences() } - columnMapDefinitions.forEach { it.checkNeedsReferences() } - typeBuilder.apply { - - writeGetModelClass(this, elementClassName) - this.writeConstructor() - associationalBehavior.writeName(this) - `override fun`(ClassNames.OBJECT_TYPE, "getType") { - modifiers(public, final) - `return`("\$T.Table", ClassNames.OBJECT_TYPE) - } - - if (updateConflictActionName.isNotEmpty()) { - `override fun`(ClassNames.CONFLICT_ACTION, "getUpdateOnConflictAction") { - modifiers(public, final) - `return`("\$T.$updateConflictActionName", ClassNames.CONFLICT_ACTION) - } - } - - if (insertConflictActionName.isNotEmpty()) { - `override fun`(ClassNames.CONFLICT_ACTION, "getInsertOnConflictAction") { - modifiers(public, final) - `return`("\$T.$insertConflictActionName", ClassNames.CONFLICT_ACTION) - } - } - - val paramColumnName = "columnName" - val getPropertiesBuilder = CodeBlock.builder() - - `override fun`(ClassNames.PROPERTY, "getProperty", - param(String::class, paramColumnName)) { - modifiers(public, final) - statement("String ${paramColumnName}2 = \$T.quoteIfNeeded($paramColumnName)", ClassNames.STRING_UTILS) - - switch("(${paramColumnName}2)") { - columnDefinitions.indices.forEach { i -> - if (i > 0) { - getPropertiesBuilder.add(",") - } - val columnDefinition = columnDefinitions[i] - elementClassName?.let { columnDefinition.addPropertyDefinition(typeBuilder, it) } - columnDefinition.addPropertyCase(this) - columnDefinition.addColumnName(getPropertiesBuilder) - } - - default { - `throw new`(IllegalArgumentException::class, "Invalid column name passed. Ensure you are calling the correct table's column") - } - } - } - - `public static final field`(ArrayTypeName.of(ClassNames.IPROPERTY), "ALL_COLUMN_PROPERTIES") { - `=`("new \$T[]{\$L}", ClassNames.IPROPERTY, getPropertiesBuilder.build().toString()) - } - - // add index properties here - for (indexGroupsDefinition in indexGroupsDefinitions) { - addField(indexGroupsDefinition.fieldSpec) - } - - if (primaryKeyColumnBehavior.hasAutoIncrement || primaryKeyColumnBehavior.hasRowID) { - val autoIncrement = primaryKeyColumnBehavior.associatedColumn - autoIncrement?.let { - `override fun`(TypeName.VOID, "updateAutoIncrement", param(elementClassName!!, ModelUtils.variable), - param(Number::class, "id")) { - modifiers(public, final) - addCode(autoIncrement.updateAutoIncrementMethod) - } - } - } - - val saveForeignKeyFields = columnDefinitions - .asSequence() - .filter { (it is ReferenceColumnDefinition) && it.foreignKeyColumnBehavior?.saveForeignKeyModel == true } - .map { it as ReferenceColumnDefinition } - .toList() - if (saveForeignKeyFields.isNotEmpty()) { - val code = CodeBlock.builder() - saveForeignKeyFields.forEach { it.appendSaveMethod(code) } - - `override fun`(TypeName.VOID, "saveForeignKeys", param(elementClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { - modifiers(public, final) - addCode(code.build()) - } - } - - val deleteForeignKeyFields = columnDefinitions - .asSequence() - .filter { (it is ReferenceColumnDefinition) && it.foreignKeyColumnBehavior?.deleteForeignKeyModel == true } - .map { it as ReferenceColumnDefinition } - .toList() - if (deleteForeignKeyFields.isNotEmpty()) { - val code = CodeBlock.builder() - deleteForeignKeyFields.forEach { it.appendDeleteMethod(code) } - - `override fun`(TypeName.VOID, "deleteForeignKeys", param(elementClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { - modifiers(public, final) - addCode(code.build()) - } - } - - `override fun`(ArrayTypeName.of(ClassNames.IPROPERTY), "getAllColumnProperties") { - modifiers(public, final) - `return`("ALL_COLUMN_PROPERTIES") - } - - creationQueryBehavior.addToType(this) - - if (cachingBehavior.cachingEnabled) { - val (_, customCacheSize, customCacheFieldName, customMultiCacheFieldName) = cachingBehavior - `public static final field`(ClassNames.CACHE_ADAPTER, "cacheAdapter") { - `=` { - val primaryColumns = primaryColumnDefinitions - - val hasCustomField = customCacheFieldName.isNotNullOrEmpty() - val hasCustomMultiCacheField = customMultiCacheFieldName.isNotNullOrEmpty() - val typeClasses = mutableListOf() - var typeArgumentsString = if (hasCustomField) { - typeClasses += elementClassName - "\$T.$customCacheFieldName" - } else { - typeClasses += ClassNames.SIMPLE_MAP_CACHE - "new \$T($customCacheSize)" - } - typeArgumentsString += ", ${primaryColumns.size.L}" - typeArgumentsString += if (hasCustomMultiCacheField) { - typeClasses += elementClassName - ", \$T.$customMultiCacheFieldName" - } else { - ", null" - } - add("\$L", - TypeSpec.anonymousClassBuilder(typeArgumentsString, *typeClasses.toTypedArray()) - .addSuperinterface(ParameterizedTypeName.get(ClassNames.CACHE_ADAPTER, elementTypeName)) - .apply { - if (primaryColumns.size > 1) { - `override fun`(ArrayTypeName.of(Any::class.java), "getCachingColumnValuesFromModel", - param(ArrayTypeName.of(Any::class.java), "inValues"), - param(elementClassName!!, ModelUtils.variable)) { - modifiers(public, final) - for (i in primaryColumns.indices) { - val column = primaryColumns[i] - addCode(column.getColumnAccessString(i)) - } - - `return`("inValues") - } - - `override fun`(ArrayTypeName.of(Any::class.java), "getCachingColumnValuesFromCursor", - param(ArrayTypeName.of(Any::class.java), "inValues"), - param(ClassNames.FLOW_CURSOR, "cursor")) { - modifiers(public, final) - for (i in primaryColumns.indices) { - val column = primaryColumns[i] - val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.complexColumnBehavior.wrapperTypeName) - statement("inValues[$i] = ${LoadFromCursorMethod.PARAM_CURSOR}" + - ".$method(${LoadFromCursorMethod.PARAM_CURSOR}.getColumnIndex(${column.columnName.S}))") - } - `return`("inValues") - } - } else { - // single primary key - `override fun`(Any::class, "getCachingColumnValueFromModel", - param(elementClassName!!, ModelUtils.variable)) { - modifiers(public, final) - addCode(primaryColumns[0].getSimpleAccessString()) - } - - `override fun`(Any::class, "getCachingColumnValueFromCursor", param(ClassNames.FLOW_CURSOR, "cursor")) { - modifiers(public, final) - val column = primaryColumns[0] - val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.complexColumnBehavior.wrapperTypeName) - `return`("${LoadFromCursorMethod.PARAM_CURSOR}.$method(${LoadFromCursorMethod.PARAM_CURSOR}.getColumnIndex(${column.columnName.S}))") - } - `override fun`(Any::class, "getCachingId", param(elementClassName!!, ModelUtils.variable)) { - modifiers(public, final) - `return`("getCachingColumnValueFromModel(${ModelUtils.variable})") - } - } - - if (foreignKeyDefinitions.isNotEmpty()) { - `override fun`(TypeName.VOID, "reloadRelationships", - param(elementClassName!!, ModelUtils.variable), - param(ClassNames.FLOW_CURSOR, LoadFromCursorMethod.PARAM_CURSOR), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { - modifiers(public, final) - code { - val noIndex = AtomicInteger(-1) - val nameAllocator = NameAllocator() - foreignKeyDefinitions.forEach { add(it.getLoadFromCursorMethod(false, noIndex, nameAllocator)) } - this - } - } - } - } - .build()) - } - } - - val singlePrimaryKey = primaryColumnDefinitions.size == 1 - - `override fun`(ClassNames.SINGLE_MODEL_LOADER, "createSingleModelLoader") { - modifiers(public, final) - addStatement("return new \$T<>(getTable(), cacheAdapter)", - if (singlePrimaryKey) - ClassNames.SINGLE_KEY_CACHEABLE_MODEL_LOADER - else - ClassNames.CACHEABLE_MODEL_LOADER) - } - `override fun`(ClassNames.LIST_MODEL_LOADER, "createListModelLoader") { - modifiers(public, final) - `return`("new \$T<>(getTable(), cacheAdapter)", - if (singlePrimaryKey) - ClassNames.SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER - else - ClassNames.CACHEABLE_LIST_MODEL_LOADER) - } - `override fun`(ParameterizedTypeName.get(ClassNames.CACHEABLE_LIST_MODEL_SAVER, elementClassName), - "createListModelSaver") { - modifiers(protected) - `return`("new \$T<>(getModelSaver(), cacheAdapter)", ClassNames.CACHEABLE_LIST_MODEL_SAVER) - } - `override fun`(TypeName.BOOLEAN, "cachingEnabled") { - modifiers(public, final) - `return`(true.L) - } - - `override fun`(elementClassName!!, "load", param(elementClassName!!, "model"), - param(ClassNames.DATABASE_WRAPPER, wrapper)) { - modifiers(public, final) - statement("\$T loaded = super.load(model, $wrapper)", elementClassName!!) - statement("cacheAdapter.storeModelInCache(model)") - `return`("loaded") - } - - } - } - - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - if (generateContentValues) { - contentValueMethods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - } - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeConverterDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeConverterDefinition.kt deleted file mode 100644 index 335cf411134a222bb15ad94b006e706ebad9454e..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeConverterDefinition.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.TypeConverter -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.TypeName -import javax.lang.model.type.DeclaredType -import javax.lang.model.type.MirroredTypesException -import javax.lang.model.type.TypeMirror - -/** - * Description: Holds data about type converters in order to write them. - */ -class TypeConverterDefinition( - typeConverter: TypeConverter?, - val className: ClassName, - typeMirror: TypeMirror, manager: ProcessorManager, - val isDefaultConverter: Boolean) { - - val modelTypeName: TypeName? - val dbTypeName: TypeName? - val allowedSubTypes: List - - init { - val allowedSubTypes: MutableList = mutableListOf() - typeConverter?.let { converter -> - try { - converter.allowedSubtypes - } catch (e: MirroredTypesException) { - val types = e.typeMirrors - types.forEach { allowedSubTypes.add(TypeName.get(it)) } - } - } - this.allowedSubTypes = allowedSubTypes - - val types = manager.typeUtils - - var typeConverterSuper: DeclaredType? = null - val typeConverterType = manager.typeUtils.getDeclaredType(manager.elements - .getTypeElement(ClassNames.TYPE_CONVERTER.toString())) - - for (superType in types.directSupertypes(typeMirror)) { - val erasure = types.erasure(superType) - if (types.isAssignable(erasure, typeConverterType) || erasure.toString() == typeConverterType.toString()) { - typeConverterSuper = superType as DeclaredType - } - } - - if (typeConverterSuper != null) { - val typeArgs = typeConverterSuper.typeArguments - dbTypeName = ClassName.get(typeArgs[0]) - modelTypeName = ClassName.get(typeArgs[1]) - } else { - dbTypeName = null - modelTypeName = null - } - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeDefinition.kt deleted file mode 100644 index 818f3bb4e937d2bdff5bee4e5107464c1ae15f10..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/TypeDefinition.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.dbflow5.processor.definition - -import com.squareup.javapoet.TypeSpec - -/** - * Description: Simple interface for returning a [TypeSpec]. - */ -interface TypeDefinition { - - /** - * @return The [TypeSpec] used to write this class' type file. - */ - val typeSpec: TypeSpec -} - diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/UniqueGroupsDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/UniqueGroupsDefinition.kt deleted file mode 100644 index 2f667314614948a0e5c7b66054bd6fcd3b806e81..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/UniqueGroupsDefinition.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.dbflow5.processor.definition - -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.UniqueGroup -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.ReferenceColumnDefinition -import com.dbflow5.quote -import com.squareup.javapoet.CodeBlock - -/** - * Description: - */ -class UniqueGroupsDefinition(uniqueGroup: UniqueGroup) { - - val columnDefinitionList: MutableList = arrayListOf() - val number: Int = uniqueGroup.groupNumber - - private val uniqueConflict: ConflictAction = uniqueGroup.uniqueConflict - - fun addColumnDefinition(columnDefinition: ColumnDefinition) { - columnDefinitionList.add(columnDefinition) - } - - val creationName: CodeBlock - get() { - val codeBuilder = CodeBlock.builder().add(", UNIQUE(") - codeBuilder.add(columnDefinitionList.joinToString { columnDefinition -> - if (columnDefinition is ReferenceColumnDefinition) { - columnDefinition.referenceDefinitionList.joinToString { it.columnName.quote() } - } else { - columnDefinition.columnName.quote() - } - }) - codeBuilder.add(") ON CONFLICT \$L", uniqueConflict) - return codeBuilder.build() - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/Behaviors.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/Behaviors.kt deleted file mode 100644 index c656bbd96fbfc02bad2f589fb169a06e6f4b4971..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/Behaviors.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.dbflow5.processor.definition.behavior - -import com.dbflow5.annotation.ModelCacheField -import com.dbflow5.annotation.MultiCacheField -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.ensureVisibleStatic -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.quote -import com.grosner.kpoet.S -import com.grosner.kpoet.`return` -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.public -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.Element -import javax.lang.model.element.TypeElement - -/** - * Defines how a class is named, db it belongs to, and other loading behaviors. - */ -data class AssociationalBehavior( - /** - * @return The name of this object in the database. Default is the class name. - */ - val name: String, - /** - * @return The class of the database this corresponds to. - */ - val databaseTypeName: TypeName, - /** - * @return When true, all public, package-private , non-static, and non-final fields of the reference class are considered as [com.dbflow5.annotation.Column] . - * The only required annotated field becomes The [PrimaryKey] - * or [PrimaryKey.autoincrement]. - */ - val allFields: Boolean) { - - fun writeName(typeSpec: TypeSpec.Builder) { - typeSpec.apply { - `override fun`(String::class, "getName") { - modifiers(public, final) - `return`(name.quote().S) - } - } - } -} - - -/** - * Defines how a Cursor gets loaded from the DB. - */ -data class CursorHandlingBehavior( - val orderedCursorLookup: Boolean = false, - val assignDefaultValuesFromCursor: Boolean = true) - -/** - * Describes caching behavior of a [TableDefinition]. - */ -data class CachingBehavior( - val cachingEnabled: Boolean, - val customCacheSize: Int, - var customCacheFieldName: String?, - var customMultiCacheFieldName: String?) { - - fun clear() { - customCacheFieldName = null - customMultiCacheFieldName = null - } - - /** - * If applicable, we store the [customCacheFieldName] or [customMultiCacheFieldName] for reference. - */ - fun evaluateElement(element: Element, typeElement: TypeElement, manager: ProcessorManager) { - if (element.annotation() != null) { - ensureVisibleStatic(element, typeElement, "ModelCacheField") - if (!customCacheFieldName.isNullOrEmpty()) { - manager.logError("ModelCacheField can only be declared once from: $typeElement") - } else { - customCacheFieldName = element.simpleName.toString() - } - } else if (element.annotation() != null) { - ensureVisibleStatic(element, typeElement, "MultiCacheField") - if (!customMultiCacheFieldName.isNullOrEmpty()) { - manager.logError("MultiCacheField can only be declared once from: $typeElement") - } else { - customMultiCacheFieldName = element.simpleName.toString() - } - } - } -} - diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ColumnBehaviors.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ColumnBehaviors.kt deleted file mode 100644 index 9bf92d59b9b248589592eb0df6d607d6b1478bbf..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ColumnBehaviors.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.dbflow5.processor.definition.behavior - -import com.dbflow5.annotation.ForeignKeyAction -import com.dbflow5.processor.definition.column.ColumnDefinition - -/** - * Defines how Primary Key columns behave. If has autoincrementing column or ROWID, the [associatedColumn] is not null. - */ -data class PrimaryKeyColumnBehavior( - val hasRowID: Boolean, - /** - * Either [hasRowID] or [hasAutoIncrement] or null. - */ - val associatedColumn: ColumnDefinition?, - val hasAutoIncrement: Boolean) - - -/** - * Defines how Foreign Key columns behave. - */ -data class ForeignKeyColumnBehavior( - val onDelete: ForeignKeyAction, - val onUpdate: ForeignKeyAction, - val saveForeignKeyModel: Boolean, - val deleteForeignKeyModel: Boolean, - val deferred: Boolean -) \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.kt deleted file mode 100644 index 390aa54e4f121cb6c06a0b169ad6d8c614ed338a..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/ComplexColumnBehavior.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.dbflow5.processor.definition.behavior - -import com.dbflow5.data.Blob -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.TypeConverterDefinition -import com.dbflow5.processor.definition.column.BlobColumnAccessor -import com.dbflow5.processor.definition.column.BooleanColumnAccessor -import com.dbflow5.processor.definition.column.ByteColumnAccessor -import com.dbflow5.processor.definition.column.CharColumnAccessor -import com.dbflow5.processor.definition.column.ColumnAccessor -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.definition.column.EnumColumnAccessor -import com.dbflow5.processor.definition.column.ReferenceDefinition -import com.dbflow5.processor.definition.column.TypeConverterScopeColumnAccessor -import com.dbflow5.processor.utils.getTypeElement -import com.squareup.javapoet.ArrayTypeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import javax.lang.model.element.Element -import javax.lang.model.element.ElementKind -import javax.lang.model.type.TypeMirror -import javax.tools.Diagnostic - -/** - * Description: Consolidates a column's wrapping behavior. - */ -class ComplexColumnBehavior( - private val columnClassName: TypeName?, - - /** - * The parent column if within a [ReferenceDefinition], or the column itself if a [ColumnDefinition]. - */ - private val columnDefinition: ColumnDefinition, - - /** - * The column that it is referencing for type information if its a [ReferenceDefinition]. - * It's itself if its a [ColumnDefinition]. - */ - private val referencedColumn: ColumnDefinition, - - - private val referencedColumnHasCustomConverter: Boolean, - typeConverterClassName: ClassName?, - typeMirror: TypeMirror?, - private val manager: ProcessorManager) { - - var hasCustomConverter: Boolean = false - var hasTypeConverter: Boolean = false - - var wrapperAccessor: ColumnAccessor? = null - var wrapperTypeName: TypeName? = null - - // Wraps for special cases such as for a Blob converter since we cannot use conventional converter - var subWrapperAccessor: ColumnAccessor? = null - - init { - handleSpecifiedTypeConverter(typeConverterClassName, typeMirror) - evaluateIfWrappingNecessary(referencedColumn.element, manager) - } - - private fun handleSpecifiedTypeConverter(typeConverterClassName: ClassName?, typeMirror: TypeMirror?) { - if (typeConverterClassName != null && typeMirror != null && - typeConverterClassName != com.dbflow5.processor.ClassNames.TYPE_CONVERTER) { - evaluateTypeConverter(TypeConverterDefinition(null, typeConverterClassName, typeMirror, manager, false), true) - } - } - - private fun evaluateIfWrappingNecessary(element: Element, - processorManager: ProcessorManager) { - val elementTypeName = referencedColumn.elementTypeName - if (!hasCustomConverter) { - val typeElement = getTypeElement(element) - if (typeElement != null && typeElement.kind == ElementKind.ENUM) { - wrapperAccessor = EnumColumnAccessor(elementTypeName!!) - wrapperTypeName = ClassName.get(String::class.java) - } else if (elementTypeName == ClassName.get(Blob::class.java)) { - wrapperAccessor = BlobColumnAccessor() - wrapperTypeName = ArrayTypeName.of(TypeName.BYTE) - } else { - if (elementTypeName is ParameterizedTypeName || - elementTypeName == ArrayTypeName.of(TypeName.BYTE.unbox())) { - // do nothing, for now. - } else if (elementTypeName is ArrayTypeName) { - processorManager.messager.printMessage(Diagnostic.Kind.ERROR, - "Columns cannot be of array type. Found $elementTypeName") - } else { - when (elementTypeName) { - TypeName.BOOLEAN -> { - wrapperAccessor = BooleanColumnAccessor() - wrapperTypeName = TypeName.BOOLEAN - } - TypeName.CHAR -> { - wrapperAccessor = CharColumnAccessor() - wrapperTypeName = TypeName.CHAR - } - TypeName.BYTE -> { - wrapperAccessor = ByteColumnAccessor() - wrapperTypeName = TypeName.BYTE - } - else -> evaluateTypeConverter(elementTypeName?.let { - processorManager.getTypeConverterDefinition(it) - }, referencedColumnHasCustomConverter) - } - } - } - } - } - - private fun evaluateTypeConverter(typeConverterDefinition: TypeConverterDefinition?, - isCustom: Boolean) { - // Any annotated members, otherwise we will use the scanner to find other ones - typeConverterDefinition?.let { typeConverter -> - - if (typeConverter.modelTypeName != columnClassName) { - manager.logError("The specified custom TypeConverter's Model Value " + - "${typeConverter.modelTypeName} from ${typeConverter.className}" + - " must match the type of the column $columnClassName.") - } else { - hasTypeConverter = true - hasCustomConverter = isCustom - - val fieldName = if (hasCustomConverter) { - columnDefinition.entityDefinition - .addColumnForCustomTypeConverter(columnDefinition, typeConverter.className) - } else { - columnDefinition.entityDefinition - .addColumnForTypeConverter(columnDefinition, typeConverter.className) - } - - wrapperAccessor = TypeConverterScopeColumnAccessor(fieldName) - wrapperTypeName = typeConverter.dbTypeName - - // special case of blob - if (wrapperTypeName == ClassName.get(Blob::class.java)) { - subWrapperAccessor = BlobColumnAccessor() - } - } - } - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.kt deleted file mode 100644 index c094a1412248d52ace4cf939522af9c3ecc8086a..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/CreationQueryBehavior.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.dbflow5.processor.definition.behavior - -import com.dbflow5.processor.definition.TypeAdder -import com.dbflow5.processor.utils.`override fun` -import com.grosner.kpoet.L -import com.grosner.kpoet.`return` -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.public -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec - -/** - * Description: - */ -data class CreationQueryBehavior(val createWithDatabase: Boolean) : TypeAdder { - - override fun addToType(typeBuilder: TypeSpec.Builder) { - typeBuilder.apply { - if (!createWithDatabase) { - `override fun`(TypeName.BOOLEAN, "createWithDatabase") { - modifiers(public, final) - `return`(false.L) - } - } - } - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/FTSBehaviors.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/FTSBehaviors.kt deleted file mode 100644 index 15988a58f6aef9e0f398608f795ab45263f87974..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/behavior/FTSBehaviors.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.dbflow5.processor.definition.behavior - -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.column.ColumnDefinition -import com.dbflow5.processor.utils.isOneOf -import com.dbflow5.quote -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeName - -interface FtsBehavior { - - val manager: ProcessorManager - val elementName: String - - fun validateColumnDefinition(columnDefinition: ColumnDefinition) { - if (columnDefinition.type != ColumnDefinition.Type.RowId - && columnDefinition.columnName != "rowid" - && columnDefinition.elementTypeName.isOneOf(Int::class, Long::class)) { - manager.logError("FTS4 Table of type $elementName can only have a single primary key named \"rowid\" of type rowid that is an Int or Long type.") - } else if (columnDefinition.elementTypeName != ClassName.get(String::class.java)) { - manager.logError("FTS4 Table of type $elementName must only contain String columns") - } - } - - fun addContentTableCode(addComma: Boolean, codeBlock: CodeBlock.Builder) { - - } -} - -/** - * Description: - */ -class FTS4Behavior( - val contentTable: TypeName, - private val databaseTypeName: TypeName, - override val elementName: String, - override val manager: ProcessorManager) : FtsBehavior { - - override fun addContentTableCode(addComma: Boolean, codeBlock: CodeBlock.Builder) { - val contentTableDefinition = manager.getTableDefinition(databaseTypeName, contentTable) - contentTableDefinition?.let { tableDefinition -> - if (addComma) { - codeBlock.add(", ") - } - codeBlock.add("content=${tableDefinition.associationalBehavior.name.quote()}") - } - } -} - -class FTS3Behavior( - override val elementName: String, - override val manager: ProcessorManager) : FtsBehavior diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessCombiner.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessCombiner.kt deleted file mode 100644 index eb2bd15a70af313b22fb26d4409cd974b1fd1bb7..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessCombiner.kt +++ /dev/null @@ -1,369 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.processor.ClassNames -import com.grosner.kpoet.S -import com.grosner.kpoet.`else` -import com.grosner.kpoet.`if` -import com.grosner.kpoet.end -import com.grosner.kpoet.statement -import com.dbflow5.processor.SQLiteHelper -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.utils.ModelUtils -import com.dbflow5.processor.utils.catch -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.statement -import com.dbflow5.quote -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.TypeName - -data class Combiner(val fieldLevelAccessor: ColumnAccessor, - val fieldTypeName: TypeName, - val wrapperLevelAccessor: ColumnAccessor? = null, - val wrapperFieldTypeName: TypeName? = null, - val subWrapperAccessor: ColumnAccessor? = null, - val customPrefixName: String = "") - -abstract class ColumnAccessCombiner(val combiner: Combiner) { - - private val nameAllocator = NameAllocator() - - fun getFieldAccessBlock(existingBuilder: CodeBlock.Builder, - modelBlock: CodeBlock, - useWrapper: Boolean = true, - defineProperty: Boolean = true): CodeBlock { - var fieldAccess: CodeBlock - combiner.apply { - if (wrapperLevelAccessor != null && !fieldTypeName.isPrimitive) { - fieldAccess = CodeBlock.of("${nameAllocator.newName(customPrefixName)}ref" + fieldLevelAccessor.propertyName) - - if (defineProperty) { - val fieldAccessorBlock = fieldLevelAccessor.get(modelBlock) - val wrapperAccessorBlock = wrapperLevelAccessor.get(fieldAccessorBlock) - // if same, don't extra null check. - if (fieldLevelAccessor.toString() != wrapperLevelAccessor.toString() - && wrapperLevelAccessor !is TypeConverterScopeColumnAccessor) { - existingBuilder.addStatement("\$T \$L = \$L != null ? \$L : null", - wrapperFieldTypeName, fieldAccess, fieldAccessorBlock, wrapperAccessorBlock) - } else if (wrapperLevelAccessor is TypeConverterScopeColumnAccessor) { - existingBuilder.addStatement("\$T \$L = \$L", wrapperFieldTypeName, - fieldAccess, wrapperAccessorBlock) - } else { - existingBuilder.addStatement("\$T \$L = \$L", wrapperFieldTypeName, - fieldAccess, fieldAccessorBlock) - } - } - } else { - fieldAccess = if (useWrapper && wrapperLevelAccessor != null) { - wrapperLevelAccessor.get(fieldLevelAccessor.get(modelBlock)) - } else { - fieldLevelAccessor.get(modelBlock) - } - } - } - return fieldAccess - } - - abstract fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock? = null, - index: Int = -1, - modelBlock: CodeBlock = CodeBlock.of("model"), - defineProperty: Boolean = true) - - open fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int = -1) { - - } -} - -class SimpleAccessCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - statement("return \$L", getFieldAccessBlock(this, modelBlock)) - } - -} - -class ExistenceAccessCombiner(combiner: Combiner, - val autoRowId: Boolean, - val quickCheckPrimaryKey: Boolean, - val tableClassName: ClassName) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - - combiner.apply { - if (autoRowId) { - val access = getFieldAccessBlock(this@addCode, modelBlock) - - add("return ") - - if (!fieldTypeName.isPrimitive) { - add("(\$L != null && ", access) - } - add("\$L > 0", access) - - if (!fieldTypeName.isPrimitive) { - add(" || \$L == null)", access) - } - } - - if (!autoRowId || !quickCheckPrimaryKey) { - if (autoRowId) { - add("\n&& ") - } else { - add("return ") - } - - add("\$T.selectCountOf()\n.from(\$T.class)\n" + - ".where(getPrimaryConditionClause(\$L))\n" + - ".hasData(wrapper)", - ClassNames.SQLITE, tableClassName, modelBlock) - } - add(";\n") - } - } - -} - -class ContentValuesCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock) - if (fieldTypeName.isPrimitive) { - statement("values.put(\$1S, \$2L)", columnRepresentation.quote(), fieldAccess) - } else { - if (defaultValue != null) { - var subWrapperFieldAccess = fieldAccess - if (subWrapperAccessor != null) { - subWrapperFieldAccess = subWrapperAccessor.get(fieldAccess) - } - if (fieldAccess.toString() != subWrapperFieldAccess.toString() - || defaultValue.toString() != "null") { - statement("values.put(\$S, \$L != null ? \$L : \$L)", - columnRepresentation.quote(), fieldAccess, subWrapperFieldAccess, defaultValue) - } else { - // if same default value is null and object reference is same as subwrapper. - statement("values.put(\$S, \$L)", - columnRepresentation.quote(), fieldAccess) - } - } else { - statement("values.put(\$S, \$L)", - columnRepresentation.quote(), fieldAccess) - } - } - } - } - - override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { - code.addStatement("values.putNull(\$S)", columnRepresentation.quote()) - } -} - -class SqliteStatementAccessCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock, - defineProperty = defineProperty) - val wrapperMethod = SQLiteHelper.getWrapperMethod(wrapperFieldTypeName ?: fieldTypeName) - val statementMethod = SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqLiteStatementMethod - - var offset = "$index + $columnRepresentation" - if (columnRepresentation.isNullOrEmpty()) { - offset = "$index" - } - if (fieldTypeName.isPrimitive) { - statement("statement.bind$statementMethod($offset, $fieldAccess)") - } else { - val subWrapperFieldAccess = subWrapperAccessor?.get(fieldAccess) ?: fieldAccess - if (!defaultValue.toString().isNullOrEmpty()) { - `if`("$fieldAccess != null") { - statement("statement.bind$wrapperMethod($offset, $subWrapperFieldAccess)") - }.`else` { - statement("statement.bind$statementMethod($offset, $defaultValue)") - } - } else { - if (subWrapperAccessor != null) { - statement("statement.bind${wrapperMethod}OrNull($offset, $fieldAccess != null ? $subWrapperFieldAccess : null)") - } else { - statement("statement.bind${wrapperMethod}OrNull($offset, $subWrapperFieldAccess)") - } - - } - } - } - } - - override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { - var access = "$index + $columnRepresentation" - if (columnRepresentation.isEmpty()) { - access = "$index" - } - code.addStatement("statement.bindNull($access)") - } -} - -class LoadFromCursorAccessCombiner(combiner: Combiner, - val hasDefaultValue: Boolean, - val nameAllocator: NameAllocator, - val cursorHandlingBehavior: CursorHandlingBehavior) - : ColumnAccessCombiner(combiner) { - - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - var indexName = if (!cursorHandlingBehavior.orderedCursorLookup) { - CodeBlock.of(columnRepresentation.S) - } else { - CodeBlock.of(index.toString()) - }!! - - if (wrapperLevelAccessor != null) { - if (!cursorHandlingBehavior.orderedCursorLookup) { - indexName = CodeBlock.of(nameAllocator.newName("index_$columnRepresentation", columnRepresentation)) - statement("\$T \$L = cursor.getColumnIndex(\$S)", Int::class.java, indexName, - columnRepresentation) - beginControlFlow("if (\$1L != -1 && !cursor.isNull(\$1L))", indexName) - } else { - beginControlFlow("if (!cursor.isNull(\$1L))", index) - } - val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) - // special case where we need to append try catch hack - val isEnum = wrapperLevelAccessor is EnumColumnAccessor - if (isEnum) { - beginControlFlow("try") - } - if (subWrapperAccessor != null) { - statement(fieldLevelAccessor.set( - wrapperLevelAccessor.set(subWrapperAccessor.set(cursorAccess)), modelBlock)) - } else { - statement(fieldLevelAccessor.set( - wrapperLevelAccessor.set(cursorAccess), modelBlock)) - } - if (isEnum) { - catch(IllegalArgumentException::class) { - if (cursorHandlingBehavior.assignDefaultValuesFromCursor) { - statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) - } else { - statement(fieldLevelAccessor.set(defaultValue, modelBlock)) - } - } - } - if (cursorHandlingBehavior.assignDefaultValuesFromCursor) { - nextControlFlow("else") - statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) - } - endControlFlow() - } else { - var hasDefault = hasDefaultValue - var defaultValueBlock = defaultValue - if (!cursorHandlingBehavior.assignDefaultValuesFromCursor) { - defaultValueBlock = fieldLevelAccessor.get(modelBlock) - } else if (!hasDefault && fieldTypeName.isBoxedPrimitive) { - hasDefault = true // force a null on it. - } - val cursorAccess = CodeBlock.of("cursor.\$LOrDefault(\$L${if (hasDefault) ", $defaultValueBlock" else ""})", - SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) - statement(fieldLevelAccessor.set(cursorAccess, modelBlock)) - } - } - } -} - -class PrimaryReferenceAccessCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock, defineProperty: Boolean) { - val wrapperLevelAccessor = this@PrimaryReferenceAccessCombiner.combiner.wrapperLevelAccessor - statement("clause.and(\$L.\$Leq(\$L))", columnRepresentation, - if (!wrapperLevelAccessor.isPrimitiveTarget()) "invertProperty()." else "", - getFieldAccessBlock(this, modelBlock, wrapperLevelAccessor !is BooleanColumnAccessor)) - } - - override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { - code.addStatement("clause.and(\$L.eq((\$T) \$L))", columnRepresentation, - ClassNames.ICONDITIONAL, "null") - } -} - -class UpdateAutoIncrementAccessCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, - index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - var method = "" - if (SQLiteHelper.containsNumberMethod(fieldTypeName.unbox())) { - method = fieldTypeName.unbox().toString() - } - - statement(fieldLevelAccessor.set(CodeBlock.of("id.\$LValue()", method), modelBlock)) - } - } - -} - -class CachingIdAccessCombiner(combiner: Combiner) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - statement("inValues[\$L] = \$L", index, getFieldAccessBlock(this, modelBlock)) - } - -} - -class SaveModelAccessCombiner(combiner: Combiner, - val implementsModel: Boolean, - val extendsBaseModel: Boolean) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - val access = getFieldAccessBlock(this@addCode, modelBlock) - `if`("$access != null") { - if (implementsModel) { - statement("$access.save(${wrapperIfBaseModel(extendsBaseModel)})") - } else { - statement("\$T.getModelAdapter(\$T.class).save($access, ${ModelUtils.wrapper})", - ClassNames.FLOW_MANAGER, fieldTypeName) - } - }.end() - } - } - -} - -class DeleteModelAccessCombiner(combiner: Combiner, - val implementsModel: Boolean, - val extendsBaseModel: Boolean) - : ColumnAccessCombiner(combiner) { - override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { - combiner.apply { - val access = getFieldAccessBlock(this@addCode, modelBlock) - `if`("$access != null") { - if (implementsModel) { - statement("$access.delete(${wrapperIfBaseModel(extendsBaseModel)})") - } else { - statement("\$T.getModelAdapter(\$T.class).delete($access, ${ModelUtils.wrapper})", - ClassNames.FLOW_MANAGER, fieldTypeName) - } - }.end() - } - } - -} - -fun wrapperIfBaseModel(extendsBaseModel: Boolean) = if (extendsBaseModel) ModelUtils.wrapper else "" -fun wrapperCommaIfBaseModel(extendsBaseModel: Boolean) = if (extendsBaseModel) ", " + ModelUtils.wrapper else "" \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessor.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessor.kt deleted file mode 100644 index 0fd8ca52c3eb19fc2e06cb8867fa5449c4a57be1..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnAccessor.kt +++ /dev/null @@ -1,285 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.data.Blob -import com.dbflow5.processor.utils.capitalizeFirstLetter -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.lower -import com.grosner.kpoet.code -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeName - -val modelBlock: CodeBlock = CodeBlock.of("model") - -/** - * Description: Base interface for accessing columns - * - * @author Andrew Grosner (fuzz) - */ -abstract class ColumnAccessor(val propertyName: String?) { - - open val isPrimitiveTarget: Boolean = false - - abstract fun get(existingBlock: CodeBlock? = null): CodeBlock - - abstract fun set(existingBlock: CodeBlock? = null, baseVariableName: CodeBlock? = null, - isDefault: Boolean = false): CodeBlock - - protected fun prependPropertyName(code: CodeBlock.Builder) { - propertyName?.let { - code.add("\$L.", propertyName) - } - } - - protected fun appendPropertyName(code: CodeBlock.Builder) { - propertyName?.let { - code.add(".\$L", propertyName) - } - } - - protected fun appendAccess(codeAccess: CodeBlock.Builder.() -> Unit): CodeBlock { - val codeBuilder = CodeBlock.builder() - prependPropertyName(codeBuilder) - codeAccess(codeBuilder) - return codeBuilder.build() - } -} - -fun ColumnAccessor?.isPrimitiveTarget(): Boolean = this?.isPrimitiveTarget ?: true - -interface GetterSetter { - - val getterName: String - val setterName: String -} - -class VisibleScopeColumnAccessor(propertyName: String) : ColumnAccessor(propertyName) { - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - val codeBlock: CodeBlock.Builder = CodeBlock.builder() - baseVariableName?.let { codeBlock.add("\$L.", baseVariableName) } - return codeBlock.add("\$L = \$L", propertyName, existingBlock) - .build() - } - - override fun get(existingBlock: CodeBlock?): CodeBlock { - val codeBlock: CodeBlock.Builder = CodeBlock.builder() - existingBlock?.let { codeBlock.add("\$L.", existingBlock) } - return codeBlock.add(propertyName) - .build() - } -} - -class PrivateScopeColumnAccessor(propertyName: String, getterSetter: GetterSetter? = null, - private val useIsForPrivateBooleans: Boolean = false, - private val optionalGetterParam: String? = "") - : ColumnAccessor(propertyName) { - - private var getterName: String = "" - private var setterName: String = "" - - override fun get(existingBlock: CodeBlock?) = code { - existingBlock?.let { this.add("$existingBlock.") } - add("$getterNameElement($optionalGetterParam)") - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean) = code { - baseVariableName?.let { add("$baseVariableName.") } - add("$setterNameElement($existingBlock)") - } - - val getterNameElement: String - get() = if (getterName.isNullOrEmpty()) { - if (propertyName != null) { - if (useIsForPrivateBooleans && !propertyName.startsWith("is", ignoreCase = true)) { - "is${propertyName.capitalize()}" - } else if (!useIsForPrivateBooleans) { - "get${propertyName.capitalize()}" - } else propertyName.lower() - } else { - "" - } - } else getterName - - val setterNameElement: String - get() = if (propertyName != null) { - var setElementName = propertyName - if (setterName.isNullOrEmpty()) { - if (!setElementName.startsWith("set", ignoreCase = true)) { - if (useIsForPrivateBooleans && setElementName.startsWith("is")) { - setElementName = setElementName.replaceFirst("is".toRegex(), "") - } else if (useIsForPrivateBooleans && setElementName.startsWith("Is")) { - setElementName = setElementName.replaceFirst("Is".toRegex(), "") - } - "set${setElementName.capitalize()}" - } else "set${setElementName.capitalize()}" - } else setterName - } else "" - - init { - getterSetter?.let { - getterName = getterSetter.getterName - setterName = getterSetter.setterName - } - } -} - -class PackagePrivateScopeColumnAccessor( - propertyName: String, packageName: String, tableClassName: String) - : ColumnAccessor(propertyName) { - - val helperClassName: ClassName - val internalHelperClassName: ClassName - - init { - helperClassName = ClassName.get(packageName, "${tableClassName}_$classSuffix") - internalHelperClassName = ClassName.get(packageName, "${tableClassName}_$classSuffix") - } - - override fun get(existingBlock: CodeBlock?): CodeBlock { - return CodeBlock.of("\$T.get\$L(\$L)", internalHelperClassName, - propertyName.capitalizeFirstLetter(), - existingBlock) - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return CodeBlock.of("\$T.set\$L(\$L, \$L)", helperClassName, - propertyName.capitalizeFirstLetter(), - baseVariableName, - existingBlock) - } - - companion object { - - val classSuffix = "Helper" - - private val methodWrittenMap = hashMapOf>() - - fun containsColumn(className: ClassName, columnName: String): Boolean { - return methodWrittenMap[className]?.contains(columnName) ?: false - } - - /** - * Ensures we only map and use a package private field generated access method if its necessary. - */ - fun putElement(className: ClassName, elementName: String) { - val list = methodWrittenMap.getOrPut(className) { arrayListOf() } - if (!list.contains(elementName)) { - list.add(elementName) - } - } - } -} - -class TypeConverterScopeColumnAccessor(val typeConverterFieldName: String, - propertyName: String? = null) - : ColumnAccessor(propertyName) { - - override fun get(existingBlock: CodeBlock?): CodeBlock { - val codeBlock = CodeBlock.builder() - codeBlock.add("\$L.getDBValue(\$L", typeConverterFieldName, existingBlock) - appendPropertyName(codeBlock) - codeBlock.add(")") - return codeBlock.build() - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - val codeBlock = CodeBlock.builder() - codeBlock.add("\$L.getModelValue(\$L", typeConverterFieldName, existingBlock) - appendPropertyName(codeBlock) - codeBlock.add(")") - return codeBlock.build() - } - -} - -class EnumColumnAccessor(val propertyTypeName: TypeName, - propertyName: String? = null) - : ColumnAccessor(propertyName) { - - override fun get(existingBlock: CodeBlock?): CodeBlock { - return appendAccess { add("\$L.name()", existingBlock) } - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return appendAccess { - if (isDefault) add(existingBlock) - else { - add("\$T.valueOf(\$L)", propertyTypeName, existingBlock) - } - } - } - -} - -class BlobColumnAccessor(propertyName: String? = null) : ColumnAccessor(propertyName) { - - override fun get(existingBlock: CodeBlock?): CodeBlock { - return appendAccess { add("\$L.getBlob()", existingBlock) } - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return appendAccess { - if (isDefault) add(existingBlock) - else add("new \$T(\$L)", ClassName.get(Blob::class.java), existingBlock) - } - } - -} - -class BooleanColumnAccessor(propertyName: String? = null) : ColumnAccessor(propertyName) { - - override fun get(existingBlock: CodeBlock?): CodeBlock { - return appendAccess { add("\$L ? 1 : 0", existingBlock) } - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return appendAccess { - if (isDefault) add(existingBlock) - else add("\$L", existingBlock) - } - } - - override val isPrimitiveTarget: Boolean = true -} - -class CharColumnAccessor(propertyName: String? = null) : ColumnAccessor(propertyName) { - - override fun get(existingBlock: CodeBlock?): CodeBlock { - return appendAccess { add("new \$T(new char[]{\$L})", TypeName.get(String::class.java), existingBlock) } - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return appendAccess { - if (isDefault) add(existingBlock) - else add("\$L.charAt(0)", existingBlock) - } - } - - override val isPrimitiveTarget: Boolean = true - -} - -class ByteColumnAccessor(propertyName: String? = null) : ColumnAccessor(propertyName) { - override fun get(existingBlock: CodeBlock?): CodeBlock { - return appendAccess { add("\$L", existingBlock) } - } - - override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - return appendAccess { - if (isDefault) add(existingBlock) - else add("(\$T) \$L", TypeName.BYTE, existingBlock) - } - } - - override val isPrimitiveTarget: Boolean = true -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnDefinition.kt deleted file mode 100644 index 42f69b4db245d61136286a1975161aed73173604..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ColumnDefinition.kt +++ /dev/null @@ -1,455 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.annotation.Collate -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.INDEX_GENERIC -import com.dbflow5.annotation.Index -import com.dbflow5.annotation.NotNull -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Unique -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BaseDefinition -import com.dbflow5.processor.definition.EntityDefinition -import com.dbflow5.processor.definition.TableDefinition -import com.dbflow5.processor.definition.behavior.ComplexColumnBehavior -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeMirrorFromAnnotation -import com.dbflow5.processor.utils.fromTypeMirror -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.toClassName -import com.dbflow5.processor.utils.toTypeElement -import com.dbflow5.quote -import com.grosner.kpoet.code -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.FieldSpec -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import java.util.concurrent.atomic.AtomicInteger -import java.util.regex.Pattern -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement - -open class ColumnDefinition @JvmOverloads -constructor( - processorManager: ProcessorManager, - element: Element, - val entityDefinition: EntityDefinition, - isPackagePrivate: Boolean, - val column: Column? = element.annotation(), - primaryKey: PrimaryKey? = element.annotation(), - notNullConflict: ConflictAction = ConflictAction.NONE -) : BaseDefinition(element, processorManager) { - - sealed class Type { - object Normal : Type() - object Primary : Type() - data class PrimaryAutoIncrement(val quickCheck: Boolean) : Type() - object RowId : Type() - - val isPrimaryField - get() = this is Primary - || this is PrimaryAutoIncrement - || this is ColumnDefinition.Type.RowId - } - - private val QUOTE_PATTERN = Pattern.compile("\".*\"") - - var columnName: String = "" - var propertyFieldName: String = "" - - var type: Type = Type.Normal - - //var isQuickCheckPrimaryKeyAutoIncrement: Boolean = false - var length = -1 - var notNull = false - var isNotNullType = false - var isNullableType = true - var onNullConflict: ConflictAction? = null - var onUniqueConflict: ConflictAction? = null - var unique = false - - var uniqueGroups: MutableList = arrayListOf() - var indexGroups: MutableList = arrayListOf() - - var collate = Collate.NONE - var defaultValue: String? = null - - var columnAccessor: ColumnAccessor - - val complexColumnBehavior: ComplexColumnBehavior - - var combiner: Combiner - - open val updateStatementBlock: CodeBlock - get() = CodeBlock.of("${columnName.quote()}=?") - - open val insertStatementColumnName: CodeBlock - get() = CodeBlock.of("\$L", columnName.quote()) - - open val insertStatementValuesString: CodeBlock? - get() = if (type is Type.PrimaryAutoIncrement && isNotNullType) { - CodeBlock.of("nullif(?, 0)") - } else { - CodeBlock.of("?") - } - - open val typeConverterElementNames: List - get() = arrayListOf(elementTypeName) - - open val primaryKeyName: String? - get() = columnName.quote() - - init { - element.annotation()?.let { notNullAnno -> - notNull = true - onNullConflict = notNullAnno.onNullConflict - } - - if (onNullConflict == ConflictAction.NONE && notNullConflict != ConflictAction.NONE) { - onNullConflict = notNullConflict - notNull = true - } - - if (elementTypeName?.isPrimitive == true) { - isNullableType = false - isNotNullType = true - } - - // if specified, usually from Kotlin targets, we will not set null on the field. - element.annotation()?.let { - isNotNullType = true - isNullableType = false - } - - // android support annotation - if (element.annotationMirrors - .any { - val className = it.annotationType.toTypeElement().toClassName() - return@any className == ClassNames.NON_NULL || className == ClassNames.NON_NULL_X - }) { - isNotNullType = true - isNullableType = false - } - - column?.let { column -> - this.columnName = when { - column.name == "" -> element.simpleName.toString() - else -> column.name - } - length = column.length - collate = column.collate - defaultValue = column.defaultValue - - if (column.defaultValue.isBlank()) { - defaultValue = null - } - - - } - if (column == null) { - this.columnName = element.simpleName.toString() - } - - val isString = (elementTypeName == ClassName.get(String::class.java)) - if (defaultValue != null - && isString - && !QUOTE_PATTERN.matcher(defaultValue).find()) { - defaultValue = "\"$defaultValue\"" - } - - if (isNotNullType && defaultValue == null - && isString) { - defaultValue = "\"\"" - } - - val nameAllocator = NameAllocator() - propertyFieldName = nameAllocator.newName(this.columnName) - - if (isPackagePrivate) { - columnAccessor = PackagePrivateScopeColumnAccessor(elementName, packageName, - ClassName.get(element.enclosingElement as TypeElement).simpleName()) - - PackagePrivateScopeColumnAccessor.putElement( - (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, - columnName) - - } else { - val isPrivate = element.modifiers.contains(Modifier.PRIVATE) - columnAccessor = if (isPrivate) { - val isBoolean = elementTypeName?.box() == TypeName.BOOLEAN.box() - val useIs = isBoolean - && entityDefinition is TableDefinition && entityDefinition.useIsForPrivateBooleans - PrivateScopeColumnAccessor(elementName, object : GetterSetter { - override val getterName: String = column?.getterName ?: "" - override val setterName: String = column?.setterName ?: "" - - }, useIsForPrivateBooleans = useIs) - } else { - VisibleScopeColumnAccessor(elementName) - } - } - - if (primaryKey != null) { - type = when { - primaryKey.rowID -> Type.RowId - primaryKey.autoincrement -> Type.PrimaryAutoIncrement(quickCheck = primaryKey.quickCheckAutoIncrement) - else -> Type.Primary - } - } - - element.annotation()?.let { uniqueColumn -> - unique = uniqueColumn.unique - onUniqueConflict = uniqueColumn.onUniqueConflict - uniqueColumn.uniqueGroups.forEach { uniqueGroups.add(it) } - } - - element.annotation()?.let { index -> - // empty index, we assume generic - if (index.indexGroups.isEmpty()) { - indexGroups.add(INDEX_GENERIC) - } else { - index.indexGroups.forEach { indexGroups.add(it) } - } - } - - val typeMirror = column?.extractTypeMirrorFromAnnotation { it.typeConverter } - val typeConverterClassName = typeMirror?.let { fromTypeMirror(typeMirror, manager) } - - complexColumnBehavior = ComplexColumnBehavior( - columnClassName = elementTypeName, - columnDefinition = this, - referencedColumn = this, - referencedColumnHasCustomConverter = false, - typeConverterClassName = typeConverterClassName, - typeMirror = typeMirror, - manager = manager - ) - - combiner = Combiner(columnAccessor, elementTypeName!!, complexColumnBehavior.wrapperAccessor, - complexColumnBehavior.wrapperTypeName, - complexColumnBehavior.subWrapperAccessor) - } - - override fun toString(): String { - val tableDef = entityDefinition - var tableName = tableDef.elementName - if (tableDef is TableDefinition) { - tableName = tableDef.associationalBehavior.name - } - return "${entityDefinition.databaseDefinition.elementName}.$tableName.${columnName.quote()}" - } - - open fun addPropertyDefinition(typeBuilder: TypeSpec.Builder, tableClass: TypeName) { - elementTypeName?.let { elementTypeName -> - val isNonPrimitiveTypeConverter = !complexColumnBehavior.wrapperAccessor.isPrimitiveTarget() - && complexColumnBehavior.wrapperAccessor is TypeConverterScopeColumnAccessor - val propParam: TypeName = if (isNonPrimitiveTypeConverter) { - ParameterizedTypeName.get(ClassNames.TYPE_CONVERTED_PROPERTY, complexColumnBehavior.wrapperTypeName, elementTypeName.box()) - } else if (!complexColumnBehavior.wrapperAccessor.isPrimitiveTarget()) { - ParameterizedTypeName.get(ClassNames.WRAPPER_PROPERTY, complexColumnBehavior.wrapperTypeName, elementTypeName.box()) - } else { - ParameterizedTypeName.get(ClassNames.PROPERTY, elementTypeName.box()) - } - - val fieldBuilder = FieldSpec.builder(propParam, - propertyFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - - if (isNonPrimitiveTypeConverter) { - val codeBlock = CodeBlock.builder() - codeBlock.add("new \$T(\$T.class, \$S, true,", propParam, tableClass, columnName) - codeBlock.add(""" - new ${"$"}T() { - @Override - public ${"$"}T getTypeConverter(Class modelClass) { - ${"$"}T adapter = (${"$"}T) ${"$"}T.getRetrievalAdapter(modelClass); - return adapter.${"$"}L; - } - })""", - ClassNames.TYPE_CONVERTER_GETTER, ClassNames.TYPE_CONVERTER, - entityDefinition.outputClassName, entityDefinition.outputClassName, - ClassNames.FLOW_MANAGER, - (complexColumnBehavior.wrapperAccessor as TypeConverterScopeColumnAccessor).typeConverterFieldName) - fieldBuilder.initializer(codeBlock.build()) - } else { - fieldBuilder.initializer("new \$T(\$T.class, \$S)", propParam, tableClass, columnName) - } - if (type is Type.Primary) { - fieldBuilder.addJavadoc("Primary Key") - } else if (type is Type.PrimaryAutoIncrement) { - fieldBuilder.addJavadoc("Primary Key AutoIncrement") - } - typeBuilder.addField(fieldBuilder.build()) - } - } - - open fun addPropertyCase(methodBuilder: MethodSpec.Builder) { - methodBuilder.apply { - beginControlFlow("case \$S: ", columnName.quote()) - addStatement("return \$L", propertyFieldName) - endControlFlow() - } - } - - open fun addColumnName(codeBuilder: CodeBlock.Builder) { - codeBuilder.add(propertyFieldName) - } - - open val contentValuesStatement: CodeBlock - get() { - val code = CodeBlock.builder() - - ContentValuesCombiner(combiner).apply { - code.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) - } - - return code.build() - } - - open fun appendIndexInitializer(initializer: CodeBlock.Builder, index: AtomicInteger) { - if (index.get() > 0) { - initializer.add(", ") - } - initializer.add(columnName) - index.incrementAndGet() - } - - open fun getSQLiteStatementMethod(index: AtomicInteger, defineProperty: Boolean = true) = code { - SqliteStatementAccessCombiner(combiner).apply { - addCode("", getDefaultValueBlock(), index.get(), modelBlock, - defineProperty) - } - this - } - - open fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger, - nameAllocator: NameAllocator) = code { - val (orderedCursorLookup, assignDefaultValuesFromCursor) = entityDefinition.cursorHandlingBehavior - var assignDefaultValue = assignDefaultValuesFromCursor - val defaultValueBlock = getDefaultValueBlock() - if (isNotNullType && CodeBlock.of("null") == defaultValueBlock) { - assignDefaultValue = false - } - - LoadFromCursorAccessCombiner(combiner, defaultValue != null, - nameAllocator, - CursorHandlingBehavior(orderedCursorLookup, assignDefaultValue)).apply { - addCode(columnName, getDefaultValueBlock(), index.get(), modelBlock) - } - this - } - - /** - * only used if [.isPrimaryKeyAutoIncrement] is true. - - * @return The statement to use. - */ - val updateAutoIncrementMethod - get() = code { - UpdateAutoIncrementAccessCombiner(combiner).apply { - addCode(columnName, getDefaultValueBlock(), 0, modelBlock) - } - this - } - - fun getColumnAccessString(index: Int) = code { - CachingIdAccessCombiner(combiner).apply { - addCode(columnName, getDefaultValueBlock(), index, modelBlock) - } - this - } - - fun getSimpleAccessString() = code { - SimpleAccessCombiner(combiner).apply { - addCode(columnName, getDefaultValueBlock(), 0, modelBlock) - } - this - } - - val quickCheckPrimaryKey: Boolean - get() = (type as? Type.PrimaryAutoIncrement)?.quickCheck ?: false - - val isAutoRowId: Boolean - get() = type is Type.RowId || type is Type.PrimaryAutoIncrement - - fun shouldWriteExistence(): Boolean { - return isAutoRowId || quickCheckPrimaryKey - } - - open fun appendExistenceMethod(codeBuilder: CodeBlock.Builder) { - ExistenceAccessCombiner(combiner, isAutoRowId, - quickCheckPrimaryKey, - entityDefinition.elementClassName!!) - .apply { - codeBuilder.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) - } - } - - open fun appendPropertyComparisonAccessStatement(codeBuilder: CodeBlock.Builder) { - PrimaryReferenceAccessCombiner(combiner).apply { - codeBuilder.addCode(propertyFieldName, getDefaultValueBlock(), 0, modelBlock) - } - } - - open val creationName: CodeBlock - get() { - val codeBlockBuilder = DefinitionUtils.getCreationStatement(elementTypeName, complexColumnBehavior.wrapperTypeName, columnName) - - if (type is Type.PrimaryAutoIncrement) { - codeBlockBuilder.add(" PRIMARY KEY ") - - if (entityDefinition is TableDefinition && - !entityDefinition.primaryKeyConflictActionName.isNullOrEmpty()) { - codeBlockBuilder.add("ON CONFLICT \$L ", entityDefinition.primaryKeyConflictActionName) - } - - codeBlockBuilder.add("AUTOINCREMENT") - } - - if (length > -1) { - codeBlockBuilder.add("(\$L)", length) - } - - if (collate != Collate.NONE) { - codeBlockBuilder.add(" COLLATE \$L", collate) - } - - if (unique) { - codeBlockBuilder.add(" UNIQUE ON CONFLICT \$L", onUniqueConflict) - } - - if (notNull) { - codeBlockBuilder.add(" NOT NULL ON CONFLICT \$L", onNullConflict) - } - - return codeBlockBuilder.build() - } - - fun getDefaultValueBlock(value: String?, elementTypeName: TypeName?): CodeBlock { - var defaultValue = value - if (defaultValue.isNullOrEmpty()) { - defaultValue = "null" - } - if (elementTypeName != null && elementTypeName.isPrimitive) { - if (elementTypeName == TypeName.BOOLEAN) { - defaultValue = "false" - } else if (elementTypeName == TypeName.BYTE || elementTypeName == TypeName.INT - || elementTypeName == TypeName.DOUBLE || elementTypeName == TypeName.FLOAT - || elementTypeName == TypeName.LONG || elementTypeName == TypeName.SHORT) { - defaultValue = "($elementTypeName) 0" - } else if (elementTypeName == TypeName.CHAR) { - defaultValue = "'\\u0000'" - } - } - return CodeBlock.of(defaultValue) - } - - fun getDefaultValueBlock() = getDefaultValueBlock(defaultValue, elementTypeName) -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/DefinitionUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/DefinitionUtils.kt deleted file mode 100644 index 07df1229921f21fe1453e74ad804d3073551b340..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/DefinitionUtils.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.processor.SQLiteHelper -import com.dbflow5.quote -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeName - -/** - * Description: - */ -object DefinitionUtils { - - fun getCreationStatement(elementTypeName: TypeName?, - wrapperTypeName: TypeName?, - columnName: String): CodeBlock.Builder { - var statement: String? = null - - if (SQLiteHelper.containsType(wrapperTypeName ?: elementTypeName)) { - statement = SQLiteHelper[wrapperTypeName ?: elementTypeName].toString() - } - - return CodeBlock.builder().add("\$L \$L", columnName.quote(), statement) - - } - - fun getLoadFromCursorMethodString(elementTypeName: TypeName?, - wrapperTypeName: TypeName?): String { - var method = "" - if (SQLiteHelper.containsMethod(wrapperTypeName ?: elementTypeName)) { - method = SQLiteHelper.getMethod(elementTypeName) - } - return method - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.kt deleted file mode 100644 index 764f44868bd59fe67ce1fff75b20fee3eab5e059..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ForeignKeyAccessCombiner.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.processor.SQLiteHelper -import com.dbflow5.processor.utils.statement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.TypeName -import java.util.concurrent.atomic.AtomicInteger - -/** - * Description: Provides structured way to combine ForeignKey for both SQLiteStatement and ContentValues - * bindings. - * - * @author Andrew Grosner (fuzz) - */ -class ForeignKeyAccessCombiner(private val fieldAccessor: ColumnAccessor) { - - var fieldAccesses: List = arrayListOf() - - fun addCode(code: CodeBlock.Builder, index: AtomicInteger, useStart: Boolean = true, - defineProperty: Boolean = true) { - val modelAccessBlock = fieldAccessor.get(modelBlock) - code.beginControlFlow("if (\$L != null)", modelAccessBlock) - val nullAccessBlock = CodeBlock.builder() - for ((i, field) in fieldAccesses.withIndex()) { - field.addCode(code, index.get(), modelAccessBlock, useStart, defineProperty) - field.addNull(nullAccessBlock, index.get(), useStart) - - // do not increment last - if (i < fieldAccesses.size - 1) { - index.incrementAndGet() - } - } - code.nextControlFlow("else") - .add(nullAccessBlock.build().toString()) - .endControlFlow() - } -} - -class ForeignKeyAccessField(private val columnRepresentation: String, - private val columnAccessCombiner: ColumnAccessCombiner, - private val defaultValue: CodeBlock? = null) { - - fun addCode(code: CodeBlock.Builder, index: Int, modelAccessBlock: CodeBlock, - useStart: Boolean = true, - defineProperty: Boolean = true) { - columnAccessCombiner.apply { - code.addCode(if (useStart) columnRepresentation else "", defaultValue, index, - modelAccessBlock, defineProperty) - } - } - - fun addNull(code: CodeBlock.Builder, index: Int, useStart: Boolean) { - columnAccessCombiner.addNull(code, if (useStart) columnRepresentation else "", index) - } -} - -class ForeignKeyLoadFromCursorCombiner(private val fieldAccessor: ColumnAccessor, - private val referencedTypeName: TypeName, - private val referencedTableTypeName: TypeName, - private val isStubbed: Boolean, - private val nameAllocator: NameAllocator) { - var fieldAccesses: List = arrayListOf() - - fun addCode(code: CodeBlock.Builder, index: AtomicInteger) { - val ifChecker = CodeBlock.builder() - val setterBlock = CodeBlock.builder() - - if (!isStubbed) { - setterBlock.add("\$T.select().from(\$T.class).where()", - com.dbflow5.processor.ClassNames.SQLITE, referencedTypeName) - } else { - setterBlock.statement( - fieldAccessor.set(CodeBlock.of("new \$T()", referencedTypeName), modelBlock)) - } - for ((i, it) in fieldAccesses.withIndex()) { - it.addRetrieval(setterBlock, index.get(), referencedTableTypeName, isStubbed, fieldAccessor, nameAllocator) - it.addColumnIndex(code, index.get(), referencedTableTypeName, nameAllocator) - it.addIndexCheckStatement(ifChecker, index.get(), referencedTableTypeName, - i == fieldAccesses.size - 1, nameAllocator) - - if (i < fieldAccesses.size - 1) { - index.incrementAndGet() - } - } - - if (!isStubbed) setterBlock.add("\n.querySingle(wrapper)") - - code.beginControlFlow("if (\$L)", ifChecker.build()) - if (!isStubbed) { - code.statement(fieldAccessor.set(setterBlock.build(), modelBlock)) - } else { - code.add(setterBlock.build()) - } - code.nextControlFlow("else") - .statement(fieldAccessor.set(CodeBlock.of("null"), modelBlock)) - .endControlFlow() - } -} - -class PartialLoadFromCursorAccessCombiner( - private val columnRepresentation: String, - private val propertyRepresentation: String, - private val fieldTypeName: TypeName, - private val orderedCursorLookup: Boolean = false, - private val fieldLevelAccessor: ColumnAccessor? = null, - private val subWrapperAccessor: ColumnAccessor? = null, - private val subWrapperTypeName: TypeName? = null) { - - private var indexName: CodeBlock? = null - - private fun getIndexName(index: Int, nameAllocator: NameAllocator, referencedTypeName: TypeName): CodeBlock { - if (indexName == null) { - indexName = if (!orderedCursorLookup) { - // post fix with referenced type name simple name - CodeBlock.of(nameAllocator.newName("index_${columnRepresentation}_" + - if (referencedTypeName is ClassName) referencedTypeName.simpleName() else "", columnRepresentation)) - } else { - CodeBlock.of(index.toString()) - } - } - return indexName!! - } - - - fun addRetrieval(code: CodeBlock.Builder, index: Int, referencedTableTypeName: TypeName, - isStubbed: Boolean, parentAccessor: ColumnAccessor, - nameAllocator: NameAllocator) { - val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(subWrapperTypeName ?: fieldTypeName), - getIndexName(index, nameAllocator, referencedTableTypeName)) - val fieldAccessBlock = subWrapperAccessor?.set(cursorAccess) ?: cursorAccess - - if (!isStubbed) { - code.add(CodeBlock.of("\n.and(\$T.\$L.eq(\$L))", - referencedTableTypeName, propertyRepresentation, fieldAccessBlock)) - } else if (fieldLevelAccessor != null) { - code.statement(fieldLevelAccessor.set(fieldAccessBlock, parentAccessor.get(modelBlock))) - } - } - - fun addColumnIndex(code: CodeBlock.Builder, index: Int, - referencedTableTypeName: TypeName, - nameAllocator: NameAllocator) { - if (!orderedCursorLookup) { - code.statement(CodeBlock.of("int \$L = cursor.getColumnIndex(\$S)", - getIndexName(index, nameAllocator, referencedTableTypeName), columnRepresentation)) - } - } - - fun addIndexCheckStatement(code: CodeBlock.Builder, index: Int, - referencedTableTypeName: TypeName, - isLast: Boolean, nameAllocator: NameAllocator) { - val indexName = getIndexName(index, nameAllocator, referencedTableTypeName) - if (!orderedCursorLookup) code.add("$indexName != -1 && ") - - code.add("!cursor.isNull($indexName)") - - if (!isLast) code.add(" && ") - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceColumnDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceColumnDefinition.kt deleted file mode 100644 index c2fd7d94553c8900cb277c69c93d22d6a02eb361..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceColumnDefinition.kt +++ /dev/null @@ -1,488 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ForeignKeyReference -import com.dbflow5.annotation.QueryModel -import com.dbflow5.annotation.Table -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ColumnValidator -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.EntityDefinition -import com.dbflow5.processor.definition.QueryModelDefinition -import com.dbflow5.processor.definition.TableDefinition -import com.dbflow5.processor.definition.behavior.ForeignKeyColumnBehavior -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeMirrorFromAnnotation -import com.dbflow5.processor.utils.fromTypeMirror -import com.dbflow5.processor.utils.implementsClass -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.isSubclass -import com.dbflow5.processor.utils.toClassName -import com.dbflow5.processor.utils.toTypeElement -import com.dbflow5.processor.utils.toTypeErasedElement -import com.dbflow5.quote -import com.dbflow5.quoteIfNeeded -import com.grosner.kpoet.S -import com.grosner.kpoet.`return` -import com.grosner.kpoet.case -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.FieldSpec -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import java.util.concurrent.atomic.AtomicInteger -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier -import javax.lang.model.type.TypeMirror - -/** - * Description: Represents both a [ForeignKey] and [ColumnMap]. Builds up the model of fields - * required to generate definitions. - */ -class ReferenceColumnDefinition -private constructor(manager: ProcessorManager, tableDefinition: EntityDefinition, - element: Element, isPackagePrivate: Boolean, - /** - * If null, its a [ColumnMap] - */ - val foreignKeyColumnBehavior: ForeignKeyColumnBehavior?, - - /** - * Foreign key references. If exists, it's precomputed first. - */ - private var references: List, - - /** - * If true, full model object does not load on cursor load. - */ - private val isStubbedRelationship: Boolean) - : ColumnDefinition(manager, element, tableDefinition, isPackagePrivate) { - - private val _referenceDefinitionList: MutableList = arrayListOf() - val referenceDefinitionList: List - get() { - checkNeedsReferences() - return _referenceDefinitionList - } - - var referencedClassName: ClassName? = null - - var isReferencingTableObject: Boolean = false - - private var implementsModel = false - private var extendsBaseModel = false - private var nonModelColumn: Boolean = false - - val isColumnMap: Boolean - get() = foreignKeyColumnBehavior == null - - private var needsReferences = true - val explicitReferences = references.isNotEmpty() - - override val typeConverterElementNames: List - get() { - val uniqueTypes = mutableSetOf() - referenceDefinitionList.filter { it.hasTypeConverter }.mapTo(uniqueTypes) { it.columnClassName } - return uniqueTypes.toList() - } - - constructor(columnMap: ColumnMap, manager: ProcessorManager, tableDefinition: EntityDefinition, - element: Element, isPackagePrivate: Boolean) : this( - manager = manager, - tableDefinition = tableDefinition, - element = element, - isPackagePrivate = isPackagePrivate, - foreignKeyColumnBehavior = null, - references = columnMap.references.map { reference -> - val typeMirror = reference.extractTypeMirrorFromAnnotation { it.typeConverter } - val typeConverterClassName = typeMirror?.let { fromTypeMirror(typeMirror, manager) } - ReferenceSpecificationDefinition(columnName = reference.columnName, - referenceName = reference.columnMapFieldName, - onNullConflictAction = reference.notNull.onNullConflict, - defaultValue = reference.defaultValue, - typeConverterClassName = typeConverterClassName, - typeConverterTypeMirror = typeMirror) - }, - // column map is always stubbed - isStubbedRelationship = true - ) { - findReferencedClassName(manager) - - // self create a column map if defined here. - typeElement?.let { typeElement -> - QueryModelDefinition(typeElement, tableDefinition.associationalBehavior.databaseTypeName, manager).apply { - manager.addQueryModelDefinition(this) - } - } - } - - constructor(foreignKey: ForeignKey, manager: ProcessorManager, tableDefinition: EntityDefinition, - element: Element, isPackagePrivate: Boolean) : - this( - manager = manager, - tableDefinition = tableDefinition, - element = element, - isPackagePrivate = isPackagePrivate, - foreignKeyColumnBehavior = ForeignKeyColumnBehavior(onDelete = foreignKey.onDelete, onUpdate = foreignKey.onUpdate, - saveForeignKeyModel = foreignKey.saveForeignKeyModel, - deleteForeignKeyModel = foreignKey.deleteForeignKeyModel, - deferred = foreignKey.deferred), - references = foreignKey.references.map { reference -> - ReferenceSpecificationDefinition(columnName = reference.columnName, - referenceName = reference.foreignKeyColumnName, - onNullConflictAction = reference.notNull.onNullConflict, - defaultValue = reference.defaultValue) - }, - isStubbedRelationship = foreignKey.stubbedRelationship - ) { - if (tableDefinition !is TableDefinition) { - manager.logError("Class $elementName cannot declare a @ForeignKey. Use @ColumnMap instead.") - } - - referencedClassName = foreignKey.extractTypeMirrorFromAnnotation { it.tableClass } - ?.let { fromTypeMirror(it, manager) } - - // hopefully intentionally left blank - if (referencedClassName == TypeName.OBJECT) { - findReferencedClassName(manager) - } - - if (referencedClassName == null) { - manager.logError("Referenced was null for $element within $elementTypeName") - } - - val erasedElement = element.toTypeErasedElement() - extendsBaseModel = erasedElement.isSubclass(manager.processingEnvironment, ClassNames.BASE_MODEL) - implementsModel = erasedElement.implementsClass(manager.processingEnvironment, ClassNames.MODEL) - isReferencingTableObject = implementsModel || erasedElement.annotation
() != null - - nonModelColumn = !isReferencingTableObject - } - - init { - if (isNotNullType) { - manager.logError("Foreign Keys must be nullable. Please remove the non-null annotation if using " + - "Java, or add ? to the type for Kotlin.") - } - } - - private fun findReferencedClassName(manager: ProcessorManager) { - if (elementTypeName is ParameterizedTypeName) { - val args = elementTypeName.typeArguments - if (args.size > 0) { - referencedClassName = ClassName.get(args[0].toTypeElement(manager)) - } - } else { - if (referencedClassName == null || referencedClassName == ClassName.OBJECT) { - referencedClassName = elementTypeName.toTypeElement().toClassName() - } - } - } - - override fun addPropertyDefinition(typeBuilder: TypeSpec.Builder, tableClass: TypeName) { - referenceDefinitionList.forEach { referenceDefinition -> - var propParam: TypeName? = null - val colClassName = referenceDefinition.columnClassName - colClassName?.let { - propParam = ParameterizedTypeName.get(ClassNames.PROPERTY, it.box()) - } - if (referenceDefinition.columnName.isNullOrEmpty()) { - manager.logError("Found empty reference name at ${referenceDefinition.foreignColumnName}" + - " from table ${entityDefinition.elementName}") - } - typeBuilder.addField(FieldSpec.builder(propParam, referenceDefinition.columnName, - Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .initializer("new \$T(\$T.class, \$S)", propParam, tableClass, referenceDefinition.columnName) - .addJavadoc( - if (isColumnMap) "Column Mapped Field" - else ("Foreign Key${if (type == Type.Primary) " / Primary Key" else ""}")) - .build()) - } - } - - override fun addPropertyCase(methodBuilder: MethodSpec.Builder) { - referenceDefinitionList.forEach { - methodBuilder.case(it.columnName.quoteIfNeeded().S) { - `return`(it.columnName) - } - } - } - - override fun appendIndexInitializer(initializer: CodeBlock.Builder, index: AtomicInteger) { - if (nonModelColumn) { - super.appendIndexInitializer(initializer, index) - } else { - referenceDefinitionList.forEach { - if (index.get() > 0) { - initializer.add(", ") - } - initializer.add(it.columnName) - index.incrementAndGet() - } - } - } - - override fun addColumnName(codeBuilder: CodeBlock.Builder) { - referenceDefinitionList.withIndex().forEach { (i, reference) -> - if (i > 0) { - codeBuilder.add(",") - } - codeBuilder.add(reference.columnName) - } - } - - override val updateStatementBlock: CodeBlock - get() { - val builder = CodeBlock.builder() - referenceDefinitionList.withIndex().forEach { (i, referenceDefinition) -> - if (i > 0) { - builder.add(",") - } - builder.add(CodeBlock.of("${referenceDefinition.columnName.quote()}=?")) - } - return builder.build() - } - - override val insertStatementColumnName: CodeBlock - get() { - val builder = CodeBlock.builder() - referenceDefinitionList.withIndex().forEach { (i, referenceDefinition) -> - if (i > 0) { - builder.add(",") - } - builder.add(referenceDefinition.columnName.quote()) - } - return builder.build() - } - - override val insertStatementValuesString: CodeBlock - get() { - val builder = CodeBlock.builder() - referenceDefinitionList.indices.forEach { i -> - if (i > 0) { - builder.add(",") - } - builder.add("?") - } - return builder.build() - } - - override val creationName: CodeBlock - get() { - val builder = CodeBlock.builder() - referenceDefinitionList.withIndex().forEach { (i, referenceDefinition) -> - if (i > 0) { - builder.add(" ,") - } - builder.add(referenceDefinition.creationStatement) - - if (referenceDefinition.notNull) { - builder.add(" NOT NULL ON CONFLICT \$L", referenceDefinition.onNullConflict) - } else if (!explicitReferences && notNull) { - builder.add(" NOT NULL ON CONFLICT \$L", onNullConflict) - } - } - return builder.build() - } - - override val primaryKeyName: String - get() { - val builder = CodeBlock.builder() - referenceDefinitionList.withIndex().forEach { (i, referenceDefinition) -> - if (i > 0) { - builder.add(" ,") - } - builder.add(referenceDefinition.primaryKeyName) - } - return builder.build().toString() - } - - override val contentValuesStatement: CodeBlock - get() = if (nonModelColumn) { - super.contentValuesStatement - } else { - val codeBuilder = CodeBlock.builder() - referencedClassName?.let { _ -> - val foreignKeyCombiner = ForeignKeyAccessCombiner(columnAccessor) - referenceDefinitionList.forEach { - foreignKeyCombiner.fieldAccesses += it.contentValuesField - } - foreignKeyCombiner.addCode(codeBuilder, AtomicInteger(0)) - } - codeBuilder.build() - } - - override fun getSQLiteStatementMethod(index: AtomicInteger, defineProperty: Boolean): CodeBlock { - if (nonModelColumn) { - return super.getSQLiteStatementMethod(index, defineProperty) - } else { - val codeBuilder = CodeBlock.builder() - referencedClassName?.let { - val foreignKeyCombiner = ForeignKeyAccessCombiner(columnAccessor) - referenceDefinitionList.forEach { - foreignKeyCombiner.fieldAccesses += it.sqliteStatementField - } - foreignKeyCombiner.addCode(codeBuilder, index, defineProperty) - } - return codeBuilder.build() - } - } - - override fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger, nameAllocator: NameAllocator): CodeBlock { - if (nonModelColumn) { - return super.getLoadFromCursorMethod(endNonPrimitiveIf, index, nameAllocator) - } else { - val code = CodeBlock.builder() - referencedClassName?.let { referencedTableClassName -> - - val tableDefinition = manager.getReferenceDefinition( - entityDefinition.databaseDefinition.elementTypeName, referencedTableClassName) - tableDefinition?.outputClassName?.let { outputClassName -> - val foreignKeyCombiner = ForeignKeyLoadFromCursorCombiner(columnAccessor, - referencedTableClassName, outputClassName, isStubbedRelationship, nameAllocator) - referenceDefinitionList.forEach { - foreignKeyCombiner.fieldAccesses += it.partialAccessor - } - foreignKeyCombiner.addCode(code, index) - } - } - return code.build() - } - } - - override fun appendPropertyComparisonAccessStatement(codeBuilder: CodeBlock.Builder) { - when { - nonModelColumn -> PrimaryReferenceAccessCombiner(combiner).apply { - codeBuilder.addCode(referenceDefinitionList[0].columnName, getDefaultValueBlock(), 0, modelBlock) - } - columnAccessor is TypeConverterScopeColumnAccessor -> super.appendPropertyComparisonAccessStatement(codeBuilder) - else -> referencedClassName?.let { _ -> - val foreignKeyCombiner = ForeignKeyAccessCombiner(columnAccessor) - referenceDefinitionList.forEach { - foreignKeyCombiner.fieldAccesses += it.primaryReferenceField - } - foreignKeyCombiner.addCode(codeBuilder, AtomicInteger(0)) - } - } - } - - fun appendSaveMethod(codeBuilder: CodeBlock.Builder) { - if (!nonModelColumn && columnAccessor !is TypeConverterScopeColumnAccessor) { - referencedClassName?.let { referencedTableClassName -> - val saveAccessor = ForeignKeyAccessField(columnName, - SaveModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, - complexColumnBehavior.wrapperAccessor, - complexColumnBehavior.wrapperTypeName, - complexColumnBehavior.subWrapperAccessor), implementsModel, extendsBaseModel)) - saveAccessor.addCode(codeBuilder, 0, modelBlock) - } - } - } - - fun appendDeleteMethod(codeBuilder: CodeBlock.Builder) { - if (!nonModelColumn && columnAccessor !is TypeConverterScopeColumnAccessor) { - referencedClassName?.let { referencedTableClassName -> - val deleteAccessor = ForeignKeyAccessField(columnName, - DeleteModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, - complexColumnBehavior.wrapperAccessor, - complexColumnBehavior.wrapperTypeName, - complexColumnBehavior.subWrapperAccessor), implementsModel, extendsBaseModel)) - deleteAccessor.addCode(codeBuilder, 0, modelBlock) - } - } - } - - /** - * If [ForeignKey] has no [ForeignKeyReference]s, we use the primary key the referenced - * table. We do this post-evaluation so all of the [TableDefinition] can be generated. - */ - fun checkNeedsReferences() { - val referencedTableDefinition = manager.getReferenceDefinition(entityDefinition.associationalBehavior.databaseTypeName, referencedClassName) - if (referencedTableDefinition == null) { - throwCannotFindReference() - } else if (needsReferences) { - val primaryColumns = - if (isColumnMap) referencedTableDefinition.columnDefinitions - else referencedTableDefinition.primaryColumnDefinitions - if (references.isEmpty()) { - primaryColumns.forEach { columnDefinition -> - val typeMirror = columnDefinition.column?.extractTypeMirrorFromAnnotation { it.typeConverter } - val typeConverterClassName = typeMirror?.let { fromTypeMirror(typeMirror, manager) } - val referenceDefinition = ReferenceDefinition(manager, - foreignKeyFieldName = elementName, - foreignKeyElementName = columnDefinition.elementName, - referencedColumn = columnDefinition, - referenceColumnDefinition = this, - referenceCount = primaryColumns.size, - localColumnName = if (isColumnMap) columnDefinition.elementName else "", - defaultValue = columnDefinition.defaultValue, - typeConverterClassName = typeConverterClassName, - typeConverterTypeMirror = typeMirror - ) - _referenceDefinitionList.add(referenceDefinition) - } - needsReferences = false - } else { - references.forEach { reference -> - val foundDefinition = primaryColumns.find { it.columnName == reference.referenceName } - if (foundDefinition == null) { - manager.logError(ReferenceColumnDefinition::class, - "Could not find referenced column ${reference.referenceName} " + - "from reference named ${reference.columnName}") - } else { - _referenceDefinitionList.add( - ReferenceDefinition(manager, - foreignKeyFieldName = elementName, - foreignKeyElementName = foundDefinition.elementName, - referencedColumn = foundDefinition, - referenceColumnDefinition = this, - referenceCount = primaryColumns.size, - localColumnName = reference.columnName, - onNullConflict = reference.onNullConflictAction, - defaultValue = reference.defaultValue, - typeConverterClassName = reference.typeConverterClassName, - typeConverterTypeMirror = reference.typeConverterTypeMirror - )) - } - } - needsReferences = false - } - - if (nonModelColumn && _referenceDefinitionList.size == 1) { - columnName = _referenceDefinitionList[0].columnName - } - - _referenceDefinitionList.forEach { - if (it.columnClassName?.isPrimitive == true - && !it.defaultValue.isNullOrEmpty()) { - manager.logWarning(ColumnValidator::class.java, - "Default value of \"${it.defaultValue}\" from " + - "${entityDefinition.elementName}.$elementName is ignored for primitive columns.") - } - } - } - } - - fun throwCannotFindReference() { - manager.logError(ReferenceColumnDefinition::class, - "Could not find the referenced ${Table::class.java.simpleName} " + - "or ${QueryModel::class.java.simpleName} definition $referencedClassName" + - " from ${entityDefinition.elementName}. " + - "Ensure it exists in the same database as ${entityDefinition.associationalBehavior.databaseTypeName}") - } -} - -/** - * Description: defines a ForeignKeyReference or ColumnMapReference. - */ -class ReferenceSpecificationDefinition(val columnName: String, - val referenceName: String, - val onNullConflictAction: ConflictAction, - val defaultValue: String, - val typeConverterClassName: ClassName? = null, - val typeConverterTypeMirror: TypeMirror? = null) \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceDefinition.kt deleted file mode 100644 index 52639e769a6b94885a4ec4b6b4fb5e2cdf04f47d..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/column/ReferenceDefinition.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.dbflow5.processor.definition.column - -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.behavior.ComplexColumnBehavior -import com.dbflow5.processor.utils.ElementUtility -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.quote -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import javax.lang.model.element.TypeElement -import javax.lang.model.type.TypeMirror - -/** - * Description: - */ -class ReferenceDefinition(private val manager: ProcessorManager, - foreignKeyFieldName: String, - foreignKeyElementName: String, - private val referencedColumn: ColumnDefinition, - private val referenceColumnDefinition: ReferenceColumnDefinition, - referenceCount: Int, localColumnName: String = "", - var onNullConflict: ConflictAction = ConflictAction.NONE, - val defaultValue: String?, - typeConverterClassName: ClassName? = null, - typeConverterTypeMirror: TypeMirror? = null) { - - val columnName: String = when { - !localColumnName.isNullOrEmpty() -> localColumnName - !referenceColumnDefinition.type.isPrimaryField || referenceCount > 0 -> - "${foreignKeyFieldName}_${referencedColumn.columnName}" - else -> foreignKeyFieldName - } - val foreignColumnName = referencedColumn.columnName - val columnClassName = referencedColumn.elementTypeName - - val notNull: Boolean - get() = onNullConflict != ConflictAction.NONE - - private val isReferencedFieldPrivate = referencedColumn.columnAccessor is PrivateScopeColumnAccessor - private val isReferencedFieldPackagePrivate: Boolean - - - internal val creationStatement: CodeBlock - get() = DefinitionUtils.getCreationStatement(columnClassName, complexColumnBehavior.wrapperTypeName, columnName).build() - - internal val primaryKeyName: String - get() = columnName.quote() - - val hasTypeConverter - get() = complexColumnBehavior.hasTypeConverter - - private val columnAccessor: ColumnAccessor - private val complexColumnBehavior: ComplexColumnBehavior - - val partialAccessor: PartialLoadFromCursorAccessCombiner - val primaryReferenceField: ForeignKeyAccessField - val contentValuesField: ForeignKeyAccessField - val sqliteStatementField: ForeignKeyAccessField - - init { - val isPackagePrivate = ElementUtility.isPackagePrivate(referencedColumn.element) - val isPackagePrivateNotInSamePackage = isPackagePrivate && - !ElementUtility.isInSamePackage(manager, referencedColumn.element, - referenceColumnDefinition.element) - - isReferencedFieldPackagePrivate = referencedColumn.columnAccessor is PackagePrivateScopeColumnAccessor - || isPackagePrivateNotInSamePackage - - val tableClassName = ClassName.get(referencedColumn.element.enclosingElement as TypeElement).simpleName() - val getterSetter = object : GetterSetter { - override val getterName: String = referencedColumn.column?.getterName ?: "" - override val setterName: String = referencedColumn.column?.setterName ?: "" - } - columnAccessor = when { - isReferencedFieldPrivate -> PrivateScopeColumnAccessor(foreignKeyElementName, getterSetter, false) - isReferencedFieldPackagePrivate -> { - val accessor = PackagePrivateScopeColumnAccessor(foreignKeyElementName, referencedColumn.packageName, - tableClassName) - PackagePrivateScopeColumnAccessor.putElement(accessor.helperClassName, foreignKeyElementName) - - accessor - } - else -> VisibleScopeColumnAccessor(foreignKeyElementName) - } - - complexColumnBehavior = ComplexColumnBehavior( - columnClassName = columnClassName, - columnDefinition = referenceColumnDefinition, - referencedColumn = referencedColumn, - referencedColumnHasCustomConverter = referencedColumn.complexColumnBehavior.hasCustomConverter, - typeConverterClassName = typeConverterClassName, - typeMirror = typeConverterTypeMirror, - manager = manager - ) - - val combiner = Combiner(columnAccessor, columnClassName!!, complexColumnBehavior.wrapperAccessor, - complexColumnBehavior.wrapperTypeName, complexColumnBehavior.subWrapperAccessor, referenceColumnDefinition.elementName) - - partialAccessor = PartialLoadFromCursorAccessCombiner( - columnRepresentation = columnName, - propertyRepresentation = foreignColumnName, - fieldTypeName = columnClassName, - orderedCursorLookup = referenceColumnDefinition.entityDefinition.cursorHandlingBehavior.orderedCursorLookup, - fieldLevelAccessor = columnAccessor, - subWrapperAccessor = complexColumnBehavior.wrapperAccessor, - subWrapperTypeName = complexColumnBehavior.wrapperTypeName - ) - - val defaultValue = referenceColumnDefinition.getDefaultValueBlock(this.defaultValue, columnClassName) - primaryReferenceField = ForeignKeyAccessField(columnName, - PrimaryReferenceAccessCombiner(combiner), defaultValue) - - contentValuesField = ForeignKeyAccessField(columnName, - ContentValuesCombiner(combiner), defaultValue) - - sqliteStatementField = ForeignKeyAccessField("", - SqliteStatementAccessCombiner(combiner), defaultValue) - } - -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProvider.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProvider.kt deleted file mode 100644 index edc38165c0bd22f5604617d42e54fda0547e2ddf..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProvider.kt +++ /dev/null @@ -1,322 +0,0 @@ -package com.dbflow5.processor.definition.provider - -import com.dbflow5.contentprovider.annotation.NotifyMethod -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.CodeAdder -import com.dbflow5.processor.definition.MethodDefinition -import com.grosner.kpoet.L -import com.grosner.kpoet.`return` -import com.grosner.kpoet.case -import com.grosner.kpoet.code -import com.grosner.kpoet.parameterized -import com.grosner.kpoet.statement -import com.squareup.javapoet.ArrayTypeName -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.TypeName -import javax.lang.model.element.Modifier - -internal fun appendDefault(code: CodeBlock.Builder) { - code.beginControlFlow("default:") - .addStatement("throw new \$T(\$S + \$L)", - ClassName.get(IllegalArgumentException::class.java), "Unknown URI", Constants.PARAM_URI) - .endControlFlow() -} - -object Constants { - - internal val PARAM_CONTENT_VALUES = "values" - internal val PARAM_URI = "uri" -} - -/** - * Get any code needed to use path segments. This should be called before creating the statement that uses - * [.getSelectionAndSelectionArgs]. - */ -internal fun ContentUriDefinition.getSegmentsPreparation() = code { - if (segments.isNotEmpty()) { - statement("\$T segments = uri.getPathSegments()", - parameterized(List::class)) - } - this -} - -/** - * Get code which creates the `selection` and `selectionArgs` parameters separated by a comma. - */ -internal fun ContentUriDefinition.getSelectionAndSelectionArgs(): CodeBlock { - if (segments.isEmpty()) { - return CodeBlock.builder().add("selection, selectionArgs").build() - } else { - val selectionBuilder = CodeBlock.builder().add("\$T.concatenateWhere(selection, \"", com.dbflow5.processor.ClassNames.DATABASE_UTILS) - val selectionArgsBuilder = CodeBlock.builder().add("\$T.appendSelectionArgs(selectionArgs, new \$T[] {", - com.dbflow5.processor.ClassNames.DATABASE_UTILS, String::class.java) - var isFirst = true - for (segment in segments) { - if (!isFirst) { - selectionBuilder.add(" AND ") - selectionArgsBuilder.add(", ") - } - selectionBuilder.add("\$L = ?", segment.column) - selectionArgsBuilder.add("segments.get(\$L)", segment.segment) - isFirst = false - } - selectionBuilder.add("\")") - selectionArgsBuilder.add("})") - return CodeBlock.builder().add(selectionBuilder.build()).add(", ").add(selectionArgsBuilder.build()).build() - } -} - -/** - * Description: - * - * @author Andrew Grosner (fuzz) - */ -class DeleteMethod(private val contentProviderDefinition: ContentProviderDefinition, - private val manager: ProcessorManager) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - val code = CodeBlock.builder() - - code.beginControlFlow("switch(MATCHER.match(\$L))", PARAM_URI) - contentProviderDefinition.endpointDefinitions.forEach { - it.contentUriDefinitions.forEach { uriDefinition -> - if (uriDefinition.deleteEnabled) { - code.apply { - case(uriDefinition.name.L) { - add(uriDefinition.getSegmentsPreparation()) - add("long count = \$T.getDatabase(\$T.class).delete(\$S, ", - com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, - it.tableName) - add(uriDefinition.getSelectionAndSelectionArgs()) - add(");\n") - - NotifyMethod(it, uriDefinition, NotifyMethod.DELETE).addCode(this) - - `return`("(int) count") - } - } - } - } - } - - appendDefault(code) - code.endControlFlow() - - return MethodSpec.methodBuilder("delete") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(com.dbflow5.processor.ClassNames.URI, PARAM_URI) - .addParameter(ClassName.get(String::class.java), PARAM_SELECTION) - .addParameter(ArrayTypeName.of(String::class.java), PARAM_SELECTION_ARGS) - .addCode(code.build()).returns(TypeName.INT).build() - } - - companion object { - - private val PARAM_URI = "uri" - private val PARAM_SELECTION = "selection" - private val PARAM_SELECTION_ARGS = "selectionArgs" - } - -} - -/** - * Description: - */ -class InsertMethod(private val contentProviderDefinition: ContentProviderDefinition, - private val isBulk: Boolean) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - val code = CodeBlock.builder() - code.beginControlFlow("switch(MATCHER.match(\$L))", Constants.PARAM_URI) - - contentProviderDefinition.endpointDefinitions.forEach { - it.contentUriDefinitions.forEach { uriDefinition -> - if (uriDefinition.insertEnabled) { - code.apply { - beginControlFlow("case \$L:", uriDefinition.name) - addStatement("\$T adapter = \$T.getModelAdapter(\$T.getTableClassForName(\$T.class, \$S))", - com.dbflow5.processor.ClassNames.MODEL_ADAPTER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, - contentProviderDefinition.databaseTypeName, it.tableName) - - add("final long id = FlowManager.getDatabase(\$T.class)", - contentProviderDefinition.databaseTypeName).add( - ".insertWithOnConflict(\$S, null, values, " + - "\$T.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));\n", it.tableName, - com.dbflow5.processor.ClassNames.CONFLICT_ACTION) - - NotifyMethod(it, uriDefinition, NotifyMethod.INSERT).addCode(this) - - if (!isBulk) { - addStatement("return \$T.withAppendedId(\$L, id)", com.dbflow5.processor.ClassNames.CONTENT_URIS, Constants.PARAM_URI) - } else { - addStatement("return id > 0 ? 1 : 0") - } - endControlFlow() - } - } - } - } - - appendDefault(code) - code.endControlFlow() - return MethodSpec.methodBuilder(if (isBulk) "bulkInsert" else "insert") - .addAnnotation(Override::class.java).addParameter(com.dbflow5.processor.ClassNames.URI, Constants.PARAM_URI) - .addParameter(com.dbflow5.processor.ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) - .addModifiers(if (isBulk) Modifier.PROTECTED else Modifier.PUBLIC, Modifier.FINAL) - .addCode(code.build()).returns(if (isBulk) TypeName.INT else com.dbflow5.processor.ClassNames.URI).build() - } - -} - -/** - * Description: - */ -class NotifyMethod(private val tableEndpointDefinition: TableEndpointDefinition, - private val uriDefinition: ContentUriDefinition, private val notifyMethod: NotifyMethod) : CodeAdder { - - override fun addCode(code: CodeBlock.Builder): CodeBlock.Builder { - var hasListener = false - val notifyDefinitionMap = tableEndpointDefinition.notifyDefinitionPathMap[uriDefinition.path] - if (notifyDefinitionMap != null) { - val notifyDefinitionList = notifyDefinitionMap[notifyMethod] - if (notifyDefinitionList != null) { - for (i in notifyDefinitionList.indices) { - val notifyDefinition = notifyDefinitionList[i] - notifyDefinition.addCode(code) - hasListener = true - } - } - } - - if (!hasListener) { - val isUpdateDelete = notifyMethod == NotifyMethod.UPDATE || notifyMethod == NotifyMethod.DELETE - if (isUpdateDelete) { - code.beginControlFlow("if (count > 0)") - } - - code.addStatement("getContext().getContentResolver().notifyChange(uri, null)") - - if (isUpdateDelete) { - code.endControlFlow() - } - } - return code - } - -} - -/** - * Description: - */ -class QueryMethod(private val contentProviderDefinition: ContentProviderDefinition, private val manager: ProcessorManager) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - val method = MethodSpec.methodBuilder("query") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(com.dbflow5.processor.ClassNames.URI, "uri") - .addParameter(ArrayTypeName.of(String::class.java), "projection") - .addParameter(ClassName.get(String::class.java), "selection") - .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") - .addParameter(ClassName.get(String::class.java), "sortOrder") - .returns(com.dbflow5.processor.ClassNames.CURSOR) - - method.addStatement("\$L cursor = null", com.dbflow5.processor.ClassNames.CURSOR) - method.beginControlFlow("switch(\$L.match(uri))", ContentProviderDefinition.URI_MATCHER) - for (tableEndpointDefinition in contentProviderDefinition.endpointDefinitions) { - tableEndpointDefinition.contentUriDefinitions - .asSequence() - .filter { it.queryEnabled } - .forEach { - method.apply { - beginControlFlow("case \$L:", it.name) - addCode(it.getSegmentsPreparation()) - addCode("cursor = \$T.getDatabase(\$T.class).query(\$S, projection, ", - com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, - tableEndpointDefinition.tableName) - addCode(it.getSelectionAndSelectionArgs()) - addCode(", null, null, sortOrder);\n") - addStatement("break") - endControlFlow() - } - } - } - method.endControlFlow() - - method.beginControlFlow("if (cursor != null)") - method.addStatement("cursor.setNotificationUri(getContext().getContentResolver(), uri)") - method.endControlFlow() - method.addStatement("return cursor") - - return method.build() - } -} - -/** - * Description: - */ -class UpdateMethod(private val contentProviderDefinition: ContentProviderDefinition, - private val manager: ProcessorManager) : MethodDefinition { - - override val methodSpec: MethodSpec? - get() { - val method = MethodSpec.methodBuilder("update") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC) - .addParameter(com.dbflow5.processor.ClassNames.URI, Constants.PARAM_URI) - .addParameter(com.dbflow5.processor.ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) - .addParameter(ClassName.get(String::class.java), "selection") - .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") - .returns(TypeName.INT) - - method.beginControlFlow("switch(MATCHER.match(\$L))", Constants.PARAM_URI) - for (tableEndpointDefinition in contentProviderDefinition.endpointDefinitions) { - tableEndpointDefinition.contentUriDefinitions - .asSequence() - .filter { it.updateEnabled } - .forEach { - method.apply { - beginControlFlow("case \$L:", it.name) - addStatement("\$T adapter = \$T.getModelAdapter(\$T.getTableClassForName(\$T.class, \$S))", - com.dbflow5.processor.ClassNames.MODEL_ADAPTER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, com.dbflow5.processor.ClassNames.FLOW_MANAGER, - contentProviderDefinition.databaseTypeName, - tableEndpointDefinition.tableName) - addCode(it.getSegmentsPreparation()) - addCode( - "long count = \$T.getDatabase(\$T.class).updateWithOnConflict(\$S, \$L, ", - com.dbflow5.processor.ClassNames.FLOW_MANAGER, contentProviderDefinition.databaseTypeName, - tableEndpointDefinition.tableName, - Constants.PARAM_CONTENT_VALUES) - addCode(it.getSelectionAndSelectionArgs()) - addCode( - ", \$T.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));\n", - com.dbflow5.processor.ClassNames.CONFLICT_ACTION) - - val code = CodeBlock.builder() - NotifyMethod(tableEndpointDefinition, it, - NotifyMethod.UPDATE).addCode(code) - addCode(code.build()) - - addStatement("return (int) count") - endControlFlow() - } - } - - } - - val code = CodeBlock.builder() - appendDefault(code) - method.addCode(code.build()) - method.endControlFlow() - - return method.build() - } - -} - diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProviderDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProviderDefinition.kt deleted file mode 100644 index f320b2961923ff27d41d8dde6b985c10398a777f..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentProviderDefinition.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.dbflow5.processor.definition.provider - -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.contentprovider.annotation.TableEndpoint -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.TableEndpointValidator -import com.dbflow5.processor.definition.BaseDefinition -import com.dbflow5.processor.definition.MethodDefinition -import com.dbflow5.processor.utils.`override fun` -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.controlFlow -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.dbflow5.processor.utils.isNullOrEmpty -import com.dbflow5.processor.utils.isSubclass -import com.dbflow5.processor.utils.toTypeElement -import com.grosner.kpoet.`=` -import com.grosner.kpoet.`break` -import com.grosner.kpoet.`private final field` -import com.grosner.kpoet.`private static final field` -import com.grosner.kpoet.`return` -import com.grosner.kpoet.code -import com.grosner.kpoet.constructor -import com.grosner.kpoet.final -import com.grosner.kpoet.modifiers -import com.grosner.kpoet.param -import com.grosner.kpoet.public -import com.grosner.kpoet.statement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.Element -import javax.lang.model.element.TypeElement - -/** - * Description: Writes the [ContentProvider] class. - */ -class ContentProviderDefinition(provider: ContentProvider, - typeElement: Element, processorManager: ProcessorManager) - : BaseDefinition(typeElement, processorManager) { - - val databaseTypeName: TypeName = provider.extractTypeNameFromAnnotation { it.database } - val endpointDefinitions = arrayListOf() - - private val authority: String = provider.authority - private val holderClass: TypeName = provider.extractTypeNameFromAnnotation { it.initializeHolderClass } - - private val methods: Array = arrayOf(QueryMethod(this, manager), - InsertMethod(this, false), - InsertMethod(this, true), - DeleteMethod(this, manager), - UpdateMethod(this, manager)) - - init { - setOutputClassName("_$DEFINITION_NAME") - - val validator = TableEndpointValidator() - val elements = manager.elements.getAllMembers(typeElement as TypeElement) - elements.forEach { element -> - element.annotation()?.let { tableEndpoint -> - val endpointDefinition = TableEndpointDefinition(tableEndpoint, element, manager) - if (validator.validate(processorManager, endpointDefinition)) { - endpointDefinitions.add(endpointDefinition) - } - } - } - - if (!databaseTypeName.toTypeElement(manager).isSubclass(manager.processingEnvironment, - ClassNames.CONTENT_PROVIDER_DATABASE)) { - manager.logError("A Content Provider database $elementClassName " + - "must extend ${ClassNames.CONTENT_PROVIDER_DATABASE}") - } - - if (holderClass != TypeName.OBJECT && - !holderClass.toTypeElement(manager).isSubclass(manager.processingEnvironment, - ClassNames.DATABASE_HOLDER)) { - manager.logError("The initializeHolderClass $holderClass must point to a subclass" + - "of ${ClassNames.DATABASE_HOLDER}") - } - } - - override val extendsClass: TypeName? = ClassNames.BASE_CONTENT_PROVIDER - - override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - - typeBuilder.apply { - if (holderClass != TypeName.OBJECT) { - constructor { - addStatement("super(\$T.class)", holderClass) - } - } - - var code = 0 - for (endpointDefinition in endpointDefinitions) { - endpointDefinition.contentUriDefinitions.forEach { - `private static final field`(TypeName.INT, it.name) { `=`(code.toString()) } - code++ - } - } - - `private final field`(ClassNames.URI_MATCHER, URI_MATCHER) { `=`("new \$T(\$T.NO_MATCH)", ClassNames.URI_MATCHER, ClassNames.URI_MATCHER) } - - `override fun`(TypeName.BOOLEAN, "onCreate") { - modifiers(public, final) - addStatement("final \$T $AUTHORITY = \$L", String::class.java, - if (authority.contains("R.string.")) - "getContext().getString($authority)" - else - "\"$authority\"") - - for (endpointDefinition in endpointDefinitions) { - endpointDefinition.contentUriDefinitions.forEach { - val path = if (!it.path.isNullOrEmpty()) { - "\"${it.path}\"" - } else { - CodeBlock.builder().add("\$L.\$L.getPath()", it.elementClassName, - it.name).build().toString() - } - addStatement("\$L.addURI(\$L, \$L, \$L)", URI_MATCHER, AUTHORITY, path, it.name) - } - } - - addStatement("return super.onCreate()") - } - - `override fun`(String::class, "getDatabaseName") { - modifiers(public, final) - `return`("\$T.getDatabaseName(\$T.class)", ClassNames.FLOW_MANAGER, databaseTypeName) - } - - `override fun`(String::class, "getType", param(ClassNames.URI, "uri")) { - modifiers(public, final) - code { - statement("\$T type = null", ClassName.get(String::class.java)) - controlFlow("switch(\$L.match(uri))", URI_MATCHER) { - endpointDefinitions.flatMap { it.contentUriDefinitions } - .forEach { uri -> - controlFlow("case \$L:", uri.name) { - statement("type = \$S", uri.type) - `break`() - } - } - appendDefault(this) - } - `return`("type") - } - } - } - - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - } - - companion object { - - internal const val DEFINITION_NAME = "Provider" - const val URI_MATCHER = "MATCHER" - private const val AUTHORITY = "AUTHORITY" - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentUriDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentUriDefinition.kt deleted file mode 100644 index 9e92f9387baf716ba393442af032109b54805191..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/ContentUriDefinition.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.dbflow5.processor.definition.provider - -import com.dbflow5.contentprovider.annotation.ContentUri -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BaseDefinition -import javax.lang.model.element.Element -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.VariableElement - -/** - * Description: - */ -class ContentUriDefinition(contentUri: ContentUri, - typeElement: Element, processorManager: ProcessorManager) - : BaseDefinition(typeElement, processorManager) { - - var name = "${typeElement.enclosingElement.simpleName}_${typeElement.simpleName}" - - val path: String = contentUri.path - val type: String = contentUri.type - val queryEnabled: Boolean = contentUri.queryEnabled - val insertEnabled: Boolean = contentUri.insertEnabled - val deleteEnabled: Boolean = contentUri.deleteEnabled - val updateEnabled: Boolean = contentUri.updateEnabled - val segments = contentUri.segments - - init { - if (typeElement is VariableElement) { - if (ClassNames.URI != elementTypeName) { - processorManager.logError("Content Uri field returned wrong type. It must return a Uri") - } - } else if (typeElement is ExecutableElement) { - if (ClassNames.URI != elementTypeName) { - processorManager.logError("ContentUri method returns wrong type. It must return Uri") - } - } - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/NotifyDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/NotifyDefinition.kt deleted file mode 100644 index 1f9088c951ff1da74e56b665241969405cdd9c33..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/NotifyDefinition.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.dbflow5.processor.definition.provider - -import com.dbflow5.contentprovider.annotation.Notify -import com.dbflow5.contentprovider.annotation.NotifyMethod -import com.dbflow5.processor.ClassNames -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BaseDefinition -import com.dbflow5.processor.definition.CodeAdder -import com.squareup.javapoet.CodeBlock -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.TypeElement - -/** - * Description: Writes code for [Notify] annotation. - */ -class NotifyDefinition(notify: Notify, - typeElement: ExecutableElement, - processorManager: ProcessorManager) - : BaseDefinition(typeElement, processorManager), CodeAdder { - - val paths: Array = notify.paths - val method: NotifyMethod = notify.notifyMethod - private val parent = (typeElement.enclosingElement as TypeElement).qualifiedName.toString() - private val methodName = typeElement.simpleName.toString() - private var params: String = typeElement.parameters.joinToString { - when (it.asType().toString()) { - "android.content.Context" -> "getContext()" - "android.net.Uri" -> "uri" - "android.content.ContentValues" -> "values" - "long" -> "id" - "java.lang.String" -> "where" - "java.lang.String[]" -> "whereArgs" - else -> "" - } - } - - var returnsArray: Boolean = false - var returnsSingle: Boolean = false - - init { - val typeMirror = typeElement.returnType - when { - "${ClassNames.URI}[]" == typeMirror.toString() -> returnsArray = true - ClassNames.URI.toString() == typeMirror.toString() -> returnsSingle = true - else -> processorManager.logError("Notify method returns wrong type. It must return Uri or Uri[]") - } - } - - override fun addCode(code: CodeBlock.Builder): CodeBlock.Builder { - if (returnsArray) { - code.addStatement("\$T[] notifyUris\$L = \$L.\$L(\$L)", ClassNames.URI, - methodName, parent, - methodName, params) - code.beginControlFlow("for (\$T notifyUri: notifyUris\$L)", ClassNames.URI, methodName) - } else { - code.addStatement("\$T notifyUri\$L = \$L.\$L(\$L)", ClassNames.URI, - methodName, parent, - methodName, params) - } - code.addStatement("getContext().getContentResolver().notifyChange(notifyUri\$L, null)", - if (returnsArray) "" else methodName) - if (returnsArray) { - code.endControlFlow() - } - return code - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/TableEndpointDefinition.kt b/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/TableEndpointDefinition.kt deleted file mode 100644 index da49e66a26ca832353a51129a13ab636934fc69e..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/definition/provider/TableEndpointDefinition.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.dbflow5.processor.definition.provider - -import com.dbflow5.contentprovider.annotation.ContentUri -import com.dbflow5.contentprovider.annotation.Notify -import com.dbflow5.contentprovider.annotation.NotifyMethod -import com.dbflow5.contentprovider.annotation.TableEndpoint -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BaseDefinition -import com.dbflow5.processor.utils.annotation -import com.dbflow5.processor.utils.extractTypeNameFromAnnotation -import com.squareup.javapoet.TypeName -import javax.lang.model.element.Element -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.PackageElement -import javax.lang.model.element.TypeElement - -/** - * Description: - */ -class TableEndpointDefinition(tableEndpoint: TableEndpoint, - typeElement: Element, processorManager: ProcessorManager) - : BaseDefinition(typeElement, processorManager) { - - var contentUriDefinitions: MutableList = mutableListOf() - - /** - * Dont want duplicate paths. - */ - internal var pathValidationMap: Map = mutableMapOf() - - var notifyDefinitionPathMap: MutableMap>> = mutableMapOf() - - var tableName: String? = null - - var contentProviderName: TypeName? = null - - var isTopLevel = false - - init { - contentProviderName = tableEndpoint.extractTypeNameFromAnnotation { - tableName = it.name - it.contentProvider - } - - isTopLevel = typeElement.enclosingElement is PackageElement - - val elements = processorManager.elements.getAllMembers(typeElement as TypeElement) - for (innerElement in elements) { - innerElement.annotation()?.let { contentUri -> - val contentUriDefinition = ContentUriDefinition(contentUri, innerElement, processorManager) - if (!pathValidationMap.containsKey(contentUriDefinition.path)) { - contentUriDefinitions.add(contentUriDefinition) - } else { - processorManager.logError("There must be unique paths " + - "for the specified @ContentUri ${contentUriDefinition.name} " + - "from $contentProviderName") - } - } - innerElement.annotation()?.let { notify -> - if (innerElement is ExecutableElement) { - val notifyDefinition = NotifyDefinition(notify, innerElement, processorManager) - @Suppress("LoopToCallChain") - for (path in notifyDefinition.paths) { - val methodListMap = notifyDefinitionPathMap.getOrPut(path) { mutableMapOf() } - val notifyDefinitionList = methodListMap.getOrPut(notifyDefinition.method) { arrayListOf() } - notifyDefinitionList.add(notifyDefinition) - } - } - } - } - - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/CodeExtensions.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/CodeExtensions.kt deleted file mode 100644 index 3216612734cf081f324622b8ace4568306d9af1e..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/CodeExtensions.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.dbflow5.processor.utils - -import com.grosner.kpoet.end -import com.grosner.kpoet.nextControl -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.MethodSpec -import kotlin.reflect.KClass - -/** - * Description: Set of utility methods to save code - * - * @author Andrew Grosner (fuzz) - */ - -/** - * Collapses the control flow into an easy to use block - */ -fun CodeBlock.Builder.controlFlow(statement: String, vararg args: Any?, - method: (CodeBlock.Builder) -> Unit) = beginControlFlow(statement, *args).apply { method(this) }.endControlFlow()!! - -fun MethodSpec.Builder.controlFlow(statement: String, vararg args: Any?, - method: CodeBlock.Builder.() -> Unit) = beginControlFlow(statement, *args).apply { - addCode(CodeBlock.builder().apply { method(this) }.build()) -}.endControlFlow()!! - -/** - * Description: Convenience method for adding [CodeBlock] statements without needing to do so every time. - * - * @author Andrew Grosner (fuzz) - */ -fun CodeBlock.Builder.statement(codeBlock: CodeBlock?): CodeBlock.Builder - = this.addStatement("\$L", codeBlock) - -fun MethodSpec.Builder.statement(codeBlock: CodeBlock?): MethodSpec.Builder - - = this.addStatement("\$L", codeBlock) - -inline fun CodeBlock.Builder.catch(exception: KClass, - function: CodeBlock.Builder.() -> CodeBlock.Builder) - = nextControl("catch", statement = "\$T e", args = *arrayOf(exception.java), function = function).end() - -fun codeBlock(function: CodeBlock.Builder.() -> CodeBlock.Builder) = CodeBlock.builder().function().build() - diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/DependencyUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/DependencyUtils.kt deleted file mode 100644 index e7d3826274bc80596981add2e9f9dfea95933fe5..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/DependencyUtils.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.processor.utils - -import com.dbflow5.processor.ProcessorManager - -/** - * Used to check if class exists on class path, if so, we add the annotation to generated class files. - */ -fun hasJavaX() = ProcessorManager.manager.elements.getTypeElement("javax.annotation.Generated") != null \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementExtensions.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementExtensions.kt deleted file mode 100644 index 85ab87a8bbfca302add4c0a3c4d17ab422d3343b..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementExtensions.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.dbflow5.processor.utils - -import com.dbflow5.processor.ProcessorManager -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import javax.lang.model.element.Element -import javax.lang.model.element.TypeElement -import javax.lang.model.type.TypeMirror -import kotlin.reflect.KClass - -// element extensions - -fun Element?.toTypeElement(manager: ProcessorManager = ProcessorManager.manager) = this?.asType().toTypeElement(manager) - - -fun Element?.toTypeErasedElement(manager: ProcessorManager = ProcessorManager.manager) = this?.asType().erasure(manager).toTypeElement(manager) - -val Element.simpleString - get() = simpleName.toString() - -// TypeMirror extensions - -fun TypeMirror?.toTypeElement(manager: ProcessorManager = ProcessorManager.manager): TypeElement? = manager.elements.getTypeElement(toString()) - -fun TypeMirror?.erasure(manager: ProcessorManager = ProcessorManager.manager): TypeMirror? = manager.typeUtils.erasure(this) - - -// TypeName - -fun TypeName?.toTypeElement(manager: ProcessorManager = ProcessorManager.manager): TypeElement? = manager.elements.getTypeElement(toString()) - -inline fun Element?.annotation() = this?.getAnnotation(T::class.java) - -fun Element?.getPackage(manager: ProcessorManager = ProcessorManager.manager) = manager.elements.getPackageOf(this) - -fun Element?.toClassName(manager: ProcessorManager = ProcessorManager.manager): ClassName? { - return when { - this == null -> null - this is TypeElement -> ClassName.get(this) - else -> ElementUtility.getClassName(asType().toString(), manager) - } -} - -fun TypeName?.isOneOf(vararg kClass: KClass<*>): Boolean = this?.let { kClass.any { clazz -> TypeName.get(clazz.java) == this } } - ?: false - -fun TypeName.rawTypeName(): TypeName { - if (this is ParameterizedTypeName) { - return rawType - } - return this -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementUtility.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementUtility.kt deleted file mode 100644 index 21bc006d53af3d62c3edbe69da8204316c598971..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/ElementUtility.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.dbflow5.processor.utils - -import com.dbflow5.annotation.ColumnIgnore -import com.dbflow5.processor.ProcessorManager -import com.squareup.javapoet.ClassName -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement -import javax.lang.model.type.TypeMirror - -/** - * Description: - */ -object ElementUtility { - - /** - * @return real full-set of elements, including ones from super-class. - */ - fun getAllElements(element: TypeElement, manager: ProcessorManager): List { - val elements = manager.elements.getAllMembers(element).toMutableList() - var superMirror: TypeMirror? - var typeElement: TypeElement? = element - while (typeElement?.superclass.let { superMirror = it; it != null }) { - typeElement = manager.typeUtils.asElement(superMirror) as TypeElement? - typeElement?.let { - val superElements = manager.elements.getAllMembers(typeElement) - superElements.forEach { if (!elements.contains(it)) elements += it } - } - } - return elements - } - - fun isInSamePackage(manager: ProcessorManager, elementToCheck: Element, original: Element): Boolean { - return manager.elements.getPackageOf(elementToCheck).toString() == manager.elements.getPackageOf(original).toString() - } - - fun isPackagePrivate(element: Element): Boolean { - return !element.modifiers.contains(Modifier.PUBLIC) && !element.modifiers.contains(Modifier.PRIVATE) - && !element.modifiers.contains(Modifier.STATIC) - } - - fun isValidAllFields(allFields: Boolean, element: Element): Boolean { - return allFields && element.kind.isField && - !element.modifiers.contains(Modifier.STATIC) && - !element.modifiers.contains(Modifier.FINAL) && - element.annotation() == null - } - - /** - * Attempts to retrieve a [ClassName] from the [elementClassname] Fully-qualified name. If it - * does not exist yet via [ClassName.get], we manually create the [ClassName] object to reference - * later at compile time validation. - */ - fun getClassName(elementClassname: String, manager: ProcessorManager): ClassName? { - val typeElement: TypeElement? = manager.elements.getTypeElement(elementClassname) - return if (typeElement != null) { - ClassName.get(typeElement) - } else { - val names = elementClassname.split(".") - if (names.isNotEmpty()) { - // attempt to take last part as class name - val className = names[names.size - 1] - ClassName.get(elementClassname.replace(".$className", ""), className) - } else { - null - } - } - } -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/JavaPoetExtensions.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/JavaPoetExtensions.kt deleted file mode 100644 index d572b3773cd3a75657e2d4f1358e7ea98e048457..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/JavaPoetExtensions.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.dbflow5.processor.utils - -import com.grosner.kpoet.returns -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.ParameterSpec -import com.squareup.javapoet.TypeName -import com.squareup.javapoet.TypeSpec -import kotlin.reflect.KClass - -fun TypeSpec.Builder.`override fun`(type: TypeName, name: String, vararg params: ParameterSpec.Builder, - codeMethod: (MethodSpec.Builder.() -> Unit) = { }) = - addMethod(MethodSpec - .methodBuilder(name) - .returns(type) - .addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .apply(codeMethod).build()) - -fun TypeSpec.Builder.`override fun`(type: KClass<*>, name: String, vararg params: ParameterSpec.Builder, - codeMethod: (MethodSpec.Builder.() -> Unit) = { }): TypeSpec.Builder = - addMethod(MethodSpec - .methodBuilder(name) - .returns(type) - .addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .apply(codeMethod).build()) - -fun `override fun`(type: TypeName, name: String, vararg params: ParameterSpec.Builder, - codeMethod: (MethodSpec.Builder.() -> Unit) = { }): MethodSpec = - MethodSpec - .methodBuilder(name) - .returns(type) - .addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .apply(codeMethod).build() - -fun `override fun`(type: KClass<*>, name: String, vararg params: ParameterSpec.Builder, - codeMethod: (MethodSpec.Builder.() -> Unit) = { }): MethodSpec = - MethodSpec - .methodBuilder(name) - .returns(type) - .addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .apply(codeMethod).build() \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/LetUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/LetUtils.kt deleted file mode 100644 index 5bc0666a54d8a9454f12d73fd20a756ed4134ba0..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/LetUtils.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.processor.utils - -/** - * Description: Multi-let execution. - */ -inline fun safeLet(a: A?, b: B?, fn: (a: A, b: B) -> R) { - if (a != null && b != null) fn(a, b) -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/ModelUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/ModelUtils.kt deleted file mode 100644 index 66bc8935fd112c82aa5a87b0b6d9f96b18c13283..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/ModelUtils.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.processor.utils - -object ModelUtils { - - val variable = "model" - - val wrapper = "wrapper" -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/ProcessorUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/ProcessorUtils.kt deleted file mode 100644 index 21cab30512cdf2254391faa71a9bef512a7e67ec..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/ProcessorUtils.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.dbflow5.processor.utils - -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.ProcessorManager.Companion.manager -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.TypeName -import javax.annotation.processing.ProcessingEnvironment -import javax.lang.model.element.Element -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement -import javax.lang.model.type.MirroredTypeException -import javax.lang.model.type.TypeMirror -import javax.tools.Diagnostic - -/** - * Whether the specified element implements the [ClassName] - */ -fun TypeElement?.implementsClass(processingEnvironment: ProcessingEnvironment - = manager.processingEnvironment, className: ClassName) = implementsClass(processingEnvironment, className.toString()) - -/** - * Whether the specified element is assignable to the fqTn parameter - - * @param processingEnvironment The environment this runs in - * * - * @param fqTn THe fully qualified type name of the element we want to check - * * - * @param element The element to check that implements - * * - * @return true if element implements the fqTn - */ -fun TypeElement?.implementsClass(processingEnvironment: ProcessingEnvironment, fqTn: String): Boolean { - val typeElement = processingEnvironment.elementUtils.getTypeElement(fqTn) - if (typeElement == null) { - processingEnvironment.messager.printMessage(Diagnostic.Kind.ERROR, - "Type Element was null for: $fqTn ensure that the visibility of the class is not private.") - return false - } else { - val classMirror: TypeMirror? = typeElement.asType().erasure() - if (classMirror == null || this?.asType() == null) { - return false - } - val elementType = this.asType() - return elementType != null && (processingEnvironment.typeUtils.isAssignable(elementType, classMirror) || elementType == classMirror) - } -} - -/** - * Whether the specified element is assignable to the [className] parameter - */ -fun TypeElement?.isSubclass(processingEnvironment: ProcessingEnvironment - = manager.processingEnvironment, className: ClassName) = isSubclass(processingEnvironment, className.toString()) - -/** - * Whether the specified element is assignable to the [fqTn] parameter - */ -fun TypeElement?.isSubclass(processingEnvironment: ProcessingEnvironment, fqTn: String): Boolean { - val typeElement = processingEnvironment.elementUtils.getTypeElement(fqTn) - if (typeElement == null) { - processingEnvironment.messager.printMessage(Diagnostic.Kind.ERROR, "Type Element was null for: $fqTn ensure that the visibility of the class is not private.") - return false - } else { - val classMirror = typeElement.asType() - return classMirror != null && this != null && this.asType() != null && processingEnvironment.typeUtils.isSubtype(this.asType(), classMirror) - } -} - -fun fromTypeMirror(typeMirror: TypeMirror, processorManager: ProcessorManager): ClassName? { - val element = getTypeElement(typeMirror) - return if (element != null) { - ClassName.get(element) - } else { - ElementUtility.getClassName(typeMirror.toString(), processorManager) - } -} - -fun getTypeElement(element: Element): TypeElement? = element as? TypeElement - ?: getTypeElement(element.asType()) - -fun getTypeElement(typeMirror: TypeMirror): TypeElement? { - val manager = manager - var typeElement: TypeElement? = typeMirror.toTypeElement(manager) - if (typeElement == null) { - val el = manager.typeUtils.asElement(typeMirror) - typeElement = if (el != null && el is TypeElement) el else null - } - return typeElement -} - -fun ensureVisibleStatic(element: Element, typeElement: TypeElement, - name: String) { - if (element.modifiers.contains(Modifier.PRIVATE) - || element.modifiers.contains(Modifier.PROTECTED)) { - manager.logError("$name must be visible from: " + typeElement) - } - if (!element.modifiers.contains(Modifier.STATIC)) { - manager.logError("$name must be static from: " + typeElement) - } - - if (!element.modifiers.contains(Modifier.FINAL)) { - manager.logError("The $name must be final") - } -} - -inline fun - Element.extractTypeNameFromAnnotation(invoker: (A) -> Unit): TypeName? = annotation()?.let { a -> - try { - invoker(a) - } catch (mte: MirroredTypeException) { - return@let TypeName.get(mte.typeMirror) - } - return@let null -} - -inline fun A.extractTypeMirrorFromAnnotation( - exceptionHandler: (MirroredTypeException) -> Unit = {}, - invoker: (A) -> Unit) - : TypeMirror? { - var mirror: TypeMirror? = null - try { - invoker(this) - } catch (mte: MirroredTypeException) { - exceptionHandler(mte) - mirror = mte.typeMirror - } - return mirror -} - -inline fun A.extractTypeNameFromAnnotation( - exceptionHandler: (MirroredTypeException) -> Unit = {}, - invoker: (A) -> Unit): TypeName = TypeName.get(extractTypeMirrorFromAnnotation(exceptionHandler, invoker)) \ No newline at end of file diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/StringUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/StringUtils.kt deleted file mode 100644 index ad76e6d478b475d0bf8ab58363bea979a17182e4..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/StringUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.dbflow5.processor.utils - -/** - * Description: - */ -fun String?.isNullOrEmpty(): Boolean { - return this == null || this.trim { it <= ' ' }.isEmpty() || this == "null" -} - -fun String?.capitalizeFirstLetter(): String { - if (this == null || this.trim { it <= ' ' }.isEmpty()) { - return this ?: "" - } - - return this.capitalize() -} - -fun String?.lower(): String { - if (this == null || this.trim { it <= ' ' }.isEmpty()) { - return this ?: "" - } - - return this.substring(0, 1).toLowerCase() + this.substring(1) -} diff --git a/processor/src/main/kotlin/com/dbflow5/processor/utils/WriterUtils.kt b/processor/src/main/kotlin/com/dbflow5/processor/utils/WriterUtils.kt deleted file mode 100644 index 19ad86fd9e7a4a8fb58ff4e23bc6ba860ed6ace8..0000000000000000000000000000000000000000 --- a/processor/src/main/kotlin/com/dbflow5/processor/utils/WriterUtils.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.dbflow5.processor.utils - -import com.dbflow5.processor.ProcessorManager -import com.dbflow5.processor.definition.BaseDefinition -import com.grosner.kpoet.javaFile -import java.io.IOException - -fun BaseDefinition.writeBaseDefinition(processorManager: ProcessorManager): Boolean { - var success = false - try { - javaFile(packageName) { typeSpec } - .writeTo(processorManager.processingEnvironment.filer) - success = true - } catch (e: IOException) { - // ignored - } catch (i: IllegalStateException) { - processorManager.logError(this::class, "Found error for class: $elementName") - processorManager.logError(this::class, i.message) - } - - return success -} \ No newline at end of file diff --git a/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors deleted file mode 100644 index 90b87177e2f5d92beebbdd671afba5057b29a00d..0000000000000000000000000000000000000000 --- a/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors +++ /dev/null @@ -1 +0,0 @@ -com.dbflow5.processor.DBFlowProcessor,isolating \ No newline at end of file diff --git a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index ba9eb39db28cf35eb748261b1efc658ae7a7d86c..0000000000000000000000000000000000000000 --- a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -com.dbflow5.processor.DBFlowProcessor \ No newline at end of file diff --git a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ColumnAccessCombinerTests.kt b/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ColumnAccessCombinerTests.kt deleted file mode 100644 index 081c494541948ae6044d26695756f67027add705..0000000000000000000000000000000000000000 --- a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ColumnAccessCombinerTests.kt +++ /dev/null @@ -1,243 +0,0 @@ -package com.dbflow5.processor.test - -import com.dbflow5.processor.definition.behavior.CursorHandlingBehavior -import com.dbflow5.processor.definition.column.BooleanColumnAccessor -import com.dbflow5.processor.definition.column.Combiner -import com.dbflow5.processor.definition.column.ContentValuesCombiner -import com.dbflow5.processor.definition.column.LoadFromCursorAccessCombiner -import com.dbflow5.processor.definition.column.PackagePrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.PrimaryReferenceAccessCombiner -import com.dbflow5.processor.definition.column.PrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.SqliteStatementAccessCombiner -import com.dbflow5.processor.definition.column.TypeConverterScopeColumnAccessor -import com.dbflow5.processor.definition.column.VisibleScopeColumnAccessor -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.TypeName -import org.junit.Assert.assertEquals -import org.junit.Test -import java.util.* - -/** - * Description: Tests to ensure we can combine [ColumnAccessor] properly. - * - * @author Andrew Grosner (fuzz) - */ -class ContentValuesCombinerTest { - - @Test - fun test_canCombineSimpleCase() { - val combiner = ContentValuesCombiner(Combiner(VisibleScopeColumnAccessor("name"), - TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", CodeBlock.of("\$S", "nonNull"), -1) - } - - assertEquals("values.put(\"`columnName`\", model.name != null ? model.name : \"nonNull\");", - codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombineSimplePrimitiveCase() { - val combiner = ContentValuesCombiner(Combiner(VisibleScopeColumnAccessor("name"), - TypeName.get(Boolean::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", index = -1) - } - - assertEquals("values.put(\"`columnName`\", model.name);", - codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombineWrapperCase() { - val combiner = ContentValuesCombiner( - Combiner(PackagePrivateScopeColumnAccessor("name", "com.fuzz.android", "TestType"), - TypeName.get(String::class.java), - TypeConverterScopeColumnAccessor("global_converter"), - TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", CodeBlock.of("\$S", "nonNull"), -1) - } - - assertEquals("java.lang.String refname = com.fuzz.android.TestType_Helper.getName(model) != null ? global_converter.getDBValue(com.fuzz.android.TestType_Helper.getName(model)) : null;" - + "\nvalues.put(\"`columnName`\", refname != null ? refname : \"nonNull\");", - codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombinePrivateWrapperCase() { - val combiner = ContentValuesCombiner( - Combiner(PrivateScopeColumnAccessor("name"), - TypeName.get(String::class.java), - TypeConverterScopeColumnAccessor("global_converter"), - TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", CodeBlock.of("\$S", "nonNull"), -1) - } - - assertEquals("java.lang.String refname = model.getName() != null ? global_converter.getDBValue(model.getName()) : null;" - + "\nvalues.put(\"`columnName`\", refname != null ? refname : \"nonNull\");", - codeBuilder.build().toString().trim()) - } - - -} - -class SqliteStatementAccessCombinerTest { - - @Test - fun test_canCombineSimpleCase() { - val combiner = SqliteStatementAccessCombiner( - Combiner(VisibleScopeColumnAccessor("name"), TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("", CodeBlock.of("\$S", "nonNull"), 0) - } - - assertEquals("if (model.name != null) {" + - "\n statement.bindString(0, model.name);" + - "\n} else {" + - "\n statement.bindString(0, \"nonNull\");" + - "\n}", codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombineSimplePrimitiveCase() { - val combiner = SqliteStatementAccessCombiner( - Combiner(VisibleScopeColumnAccessor("name"), - TypeName.get(Boolean::class.java), - BooleanColumnAccessor(), - TypeName.get(Boolean::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("", index = 0) - } - - assertEquals("statement.bindLong(0, model.name ? 1 : 0);", - codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombineWrapperCase() { - val combiner = SqliteStatementAccessCombiner( - Combiner(PackagePrivateScopeColumnAccessor("name", "com.fuzz.android", "TestType"), - TypeName.get(String::class.java), - TypeConverterScopeColumnAccessor("global_converter"), - TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("", CodeBlock.of("\$S", "nonNull"), 1) - } - - assertEquals("java.lang.String refname = com.fuzz.android.TestType_Helper.getName(model) != null ? global_converter.getDBValue(com.fuzz.android.TestType_Helper.getName(model)) : null;" + - "\nif (refname != null) {" + - "\n statement.bindString(1, refname);" + - "\n} else {" + - "\n statement.bindString(1, \"nonNull\");" + - "\n}", codeBuilder.build().toString().trim()) - } - - @Test - fun test_canCombinePrivateWrapperCase() { - val combiner = SqliteStatementAccessCombiner( - Combiner(PrivateScopeColumnAccessor("name"), - TypeName.get(String::class.java), - TypeConverterScopeColumnAccessor("global_converter"), - TypeName.get(String::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("", CodeBlock.of("\$S", "nonNull"), 1) - } - assertEquals("java.lang.String refname = model.getName() != null ? global_converter.getDBValue(model.getName()) : null;" + - "\nif (refname != null) {" + - "\n statement.bindString(1, refname);" + - "\n} else {" + - "\n statement.bindString(1, \"nonNull\");" + - "\n}", codeBuilder.build().toString().trim()) - } - -} - -class LoadFromCursorAccessCombinerTest { - - @Test - fun test_simpleCase() { - val combiner = LoadFromCursorAccessCombiner( - Combiner(VisibleScopeColumnAccessor("name"), TypeName.get(String::class.java)), false, NameAllocator(), - CursorHandlingBehavior()) - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", CodeBlock.of("\$S", "nonNull")) - } - - assertEquals("model.name = cursor.getStringOrDefault(\"columnName\");", codeBuilder.build().toString().trim()) - } - - @Test - fun test_wrapperCase() { - val combiner = LoadFromCursorAccessCombiner( - Combiner(VisibleScopeColumnAccessor("name"), - TypeName.get(Date::class.java), - wrapperLevelAccessor = TypeConverterScopeColumnAccessor("global_converter"), - wrapperFieldTypeName = TypeName.get(String::class.java)), false, NameAllocator(), - CursorHandlingBehavior()) - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("columnName", CodeBlock.of("\$S", "nonNull")) - } - - assertEquals("int index_columnName = cursor.getColumnIndex(\"columnName\");" + - "\nif (index_columnName != -1 && !cursor.isNull(index_columnName)) {" + - "\n model.name = global_converter.getModelValue(cursor.getString(index_columnName));" + - "\n} else {" + - "\n model.name = global_converter.getModelValue(\"nonNull\");" + - "\n}", codeBuilder.build().toString().trim()) - } -} - -class PrimaryReferenceAccessCombinerTest { - - @Test - fun test_simpleCase() { - val combiner = PrimaryReferenceAccessCombiner( - Combiner(VisibleScopeColumnAccessor("id"), TypeName.get(Long::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("id") - } - - assertEquals("clause.and(id.eq(model.id));", codeBuilder.build().toString().trim()) - } - - @Test - fun test_typeConverterCase() { - val combiner = PrimaryReferenceAccessCombiner( - Combiner(VisibleScopeColumnAccessor("id"), - TypeName.get(Long::class.java).box(), - TypeConverterScopeColumnAccessor("global_converter"), - TypeName.get(Date::class.java))) - - val codeBuilder = CodeBlock.builder() - combiner.apply { - codeBuilder.addCode("id") - } - - assertEquals("java.util.Date refid = model.id != null ? global_converter.getDBValue(model.id) : null;\n" + - "clause.and(id.invertProperty().eq(refid));", - codeBuilder.build().toString().trim()) - } -} \ No newline at end of file diff --git a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ForeignKeyAccessCombinerTests.kt b/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ForeignKeyAccessCombinerTests.kt deleted file mode 100644 index b9f1af464e1489ee8f56099a346482c39cc9092b..0000000000000000000000000000000000000000 --- a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/ForeignKeyAccessCombinerTests.kt +++ /dev/null @@ -1,165 +0,0 @@ -package com.dbflow5.processor.test - -import com.dbflow5.processor.definition.column.Combiner -import com.dbflow5.processor.definition.column.ContentValuesCombiner -import com.dbflow5.processor.definition.column.ForeignKeyAccessCombiner -import com.dbflow5.processor.definition.column.ForeignKeyAccessField -import com.dbflow5.processor.definition.column.ForeignKeyLoadFromCursorCombiner -import com.dbflow5.processor.definition.column.PackagePrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.PartialLoadFromCursorAccessCombiner -import com.dbflow5.processor.definition.column.PrimaryReferenceAccessCombiner -import com.dbflow5.processor.definition.column.PrivateScopeColumnAccessor -import com.dbflow5.processor.definition.column.SqliteStatementAccessCombiner -import com.dbflow5.processor.definition.column.TypeConverterScopeColumnAccessor -import com.dbflow5.processor.definition.column.VisibleScopeColumnAccessor -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.TypeName -import org.junit.Assert.assertEquals -import org.junit.Test -import java.util.* -import java.util.concurrent.atomic.AtomicInteger - -/** - * Description: - * - * @author Andrew Grosner (fuzz) - */ - - -class ForeignKeyAccessCombinerTest { - - @Test - fun test_canCombineSimpleCase() { - val foreignKeyAccessCombiner = ForeignKeyAccessCombiner(VisibleScopeColumnAccessor("name")) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("test", - ContentValuesCombiner(Combiner(VisibleScopeColumnAccessor("test"), TypeName.get(String::class.java)))) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("test2", - ContentValuesCombiner(Combiner(PrivateScopeColumnAccessor("test2"), TypeName.get(Int::class.java)))) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(4)) - - assertEquals("if (model.name != null) {" + - "\n values.put(\"`test`\", model.name.test);" + - "\n values.put(\"`test2`\", model.name.getTest2());" + - "\n} else {" + - "\n values.putNull(\"`test`\");" + - "\n values.putNull(\"`test2`\");" + - "\n}", - builder.build().toString().trim()) - } - - @Test - fun test_canCombineSimplePrivateCase() { - val foreignKeyAccessCombiner = ForeignKeyAccessCombiner(PrivateScopeColumnAccessor("name")) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("", - SqliteStatementAccessCombiner(Combiner(VisibleScopeColumnAccessor("test"), TypeName.get(String::class.java)))) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(4)) - - assertEquals("if (model.getName() != null) {" + - "\n statement.bindStringOrNull(4, model.getName().test);" + - "\n} else {" + - "\n statement.bindNull(4);" + - "\n}", - builder.build().toString().trim()) - } - - @Test - fun test_canCombinePackagePrivateCase() { - val foreignKeyAccessCombiner = ForeignKeyAccessCombiner(PackagePrivateScopeColumnAccessor("name", - "com.fuzz.android", "TestHelper")) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("test", - PrimaryReferenceAccessCombiner(Combiner(PackagePrivateScopeColumnAccessor("test", - "com.fuzz.android", "TestHelper2"), - TypeName.get(String::class.java)))) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(4)) - - assertEquals("if (com.fuzz.android.TestHelper_Helper.getName(model) != null) {" + - "\n clause.and(test.eq(com.fuzz.android.TestHelper2_Helper.getTest(com.fuzz.android.TestHelper_Helper.getName(model))));" + - "\n} else {" + - "\n clause.and(test.eq((com.dbflow5.sql.language.IConditional) null));" + - "\n}", - builder.build().toString().trim()) - } - - @Test - fun test_canDoComplexCase() { - val foreignKeyAccessCombiner = ForeignKeyAccessCombiner(VisibleScopeColumnAccessor("modem")) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("number", - ContentValuesCombiner(Combiner(PackagePrivateScopeColumnAccessor("number", - "com.fuzz", "AnotherHelper"), - TypeName.INT))) - foreignKeyAccessCombiner.fieldAccesses += ForeignKeyAccessField("date", - ContentValuesCombiner(Combiner(TypeConverterScopeColumnAccessor("global_converter", "date"), - TypeName.get(Date::class.java)))) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(1)) - - assertEquals("if (model.modem != null) {" + - "\n values.put(\"`number`\", com.fuzz.AnotherHelper\$Helper.getNumber(model.modem));" + - "\n values.put(\"`date`\", global_converter.getDBValue(model.modem.date));" + - "\n} else {" + - "\n values.putNull(\"`number`\");" + - "\n values.putNull(\"`date`\");" + - "\n}", - builder.build().toString().trim()) - } - - @Test - fun test_canLoadFromCursor() { - val foreignKeyAccessCombiner = ForeignKeyLoadFromCursorCombiner(VisibleScopeColumnAccessor("testModel1"), - ClassName.get("com.dbflow5.test.container", "ParentModel"), - ClassName.get("com.dbflow5.test.container", "ParentModel_Table"), false, - NameAllocator()) - foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_id", - "name", TypeName.get(String::class.java), false, null) - foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_type", - "type", TypeName.get(String::class.java), false, null) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(0)) - - assertEquals("int index_testmodel_id_ParentModel_Table = cursor.getColumnIndex(\"testmodel_id\");" + - "\nint index_testmodel_type_ParentModel_Table = cursor.getColumnIndex(\"testmodel_type\");" + - "\nif (index_testmodel_id_ParentModel_Table != -1 && !cursor.isNull(index_testmodel_id_ParentModel_Table) && index_testmodel_type_ParentModel_Table != -1 && !cursor.isNull(index_testmodel_type_ParentModel_Table)) {" + - "\n model.testModel1 = com.dbflow5.sql.language.SQLite.select().from(com.dbflow5.test.container.ParentModel.class).where()" + - "\n .and(com.dbflow5.test.container.ParentModel_Table.name.eq(cursor.getString(index_testmodel_id_ParentModel_Table)))" + - "\n .and(com.dbflow5.test.container.ParentModel_Table.type.eq(cursor.getString(index_testmodel_type_ParentModel_Table)))" + - "\n .querySingle();" + - "\n} else {" + - "\n model.testModel1 = null;" + - "\n}", builder.build().toString().trim()) - } - - @Test - fun test_canLoadFromCursorStubbed() { - val foreignKeyAccessCombiner = ForeignKeyLoadFromCursorCombiner(VisibleScopeColumnAccessor("testModel1"), - ClassName.get("com.dbflow5.test.container", "ParentModel"), - ClassName.get("com.dbflow5.test.container", "ParentModel_Table"), true, - NameAllocator()) - foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_id", - "name", TypeName.get(String::class.java), false, VisibleScopeColumnAccessor("name")) - foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_type", - "type", TypeName.get(String::class.java), false, VisibleScopeColumnAccessor("type")) - - val builder = CodeBlock.builder() - foreignKeyAccessCombiner.addCode(builder, AtomicInteger(0)) - - assertEquals("int index_testmodel_id_ParentModel_Table = cursor.getColumnIndex(\"testmodel_id\");" + - "\nint index_testmodel_type_ParentModel_Table = cursor.getColumnIndex(\"testmodel_type\");" + - "\nif (index_testmodel_id_ParentModel_Table != -1 && !cursor.isNull(index_testmodel_id_ParentModel_Table) && index_testmodel_type_ParentModel_Table != -1 && !cursor.isNull(index_testmodel_type_ParentModel_Table)) {" + - "\n model.testModel1 = new com.dbflow5.test.container.ParentModel();" + - "\n model.testModel1.name = cursor.getString(index_testmodel_id_ParentModel_Table);" + - "\n model.testModel1.type = cursor.getString(index_testmodel_type_ParentModel_Table);" + - "\n} else {" + - "\n model.testModel1 = null;" + - "\n}", builder.build().toString().trim()) - } -} \ No newline at end of file diff --git a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/SimpleColumnAccessorTests.kt b/processor/src/test/java/com/raizlabs/dbflow5/processor/test/SimpleColumnAccessorTests.kt deleted file mode 100644 index 0b771de0ff3f9391430828f333ae65bc45a963f3..0000000000000000000000000000000000000000 --- a/processor/src/test/java/com/raizlabs/dbflow5/processor/test/SimpleColumnAccessorTests.kt +++ /dev/null @@ -1,171 +0,0 @@ -package com.dbflow5.processor.test - -import com.dbflow5.processor.definition.column.* -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.TypeName -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test - -/** - * Description: - * - * @author Andrew Grosner (fuzz) - */ -class VisibleScopeColumnAccessorTest { - - - @Test - fun test_canSetSimpleAccess() { - - val simpleAccess = VisibleScopeColumnAccessor("test") - assertEquals("test = something", simpleAccess.set(CodeBlock.of("something")).toString()) - } - - @Test - fun test_canGetSimpleAccess() { - val simpleAccess = VisibleScopeColumnAccessor("test") - assertEquals("test", simpleAccess.get().toString()) - } -} - -class PrivateScopeColumnAccessorTest { - - @Test - fun test_canGetPrivateIsAccess() { - val privateAccess = PrivateScopeColumnAccessor("isTest", - useIsForPrivateBooleans = true) - - assertEquals("isTest()", privateAccess.get().toString()) - } - - @Test - fun test_canGetSimplePrivate() { - val privateAccess = PrivateScopeColumnAccessor("isTest") - assertEquals("getIsTest()", privateAccess.get().toString()) - } - - @Test - fun test_canRetrieveColumnGetterSetter() { - val privateAccess = PrivateScopeColumnAccessor("isTest", - object : GetterSetter { - override val getterName = "getTest" - override val setterName = "isTest" - }) - assertEquals("getTest()", privateAccess.get().toString()) - assertEquals("isTest(yellow)", privateAccess.set(CodeBlock.of("yellow")).toString()) - } -} - -class PackagePrivateScopeColumnAccessorTest { - - @Test - fun test_canSetupClass() { - val access = PackagePrivateScopeColumnAccessor("test", "com.fuzz.android", "TestClass") - assertEquals("com.fuzz.android.TestClass_Helper", access.helperClassName.toString()) - assertEquals("com.fuzz.android.TestClass_Helper", access.internalHelperClassName.toString()) - } - - @Test - fun test_canGetVariable() { - val access = PackagePrivateScopeColumnAccessor("test", "com.fuzz.android", "TestClass") - assertEquals("com.fuzz.android.TestClass_Helper.getTest(model)", access.get(CodeBlock.of("model")).toString()) - } - - @Test - fun test_canSetVariable() { - val access = PackagePrivateScopeColumnAccessor("test", "com.fuzz.android", "TestClass") - assertEquals("com.fuzz.android.TestClass_Helper.setTest(model, \"name\")", - access.set(CodeBlock.of("\$S", "name"), CodeBlock.of("model")).toString()) - } - -} - -class TypeConverterScopeColumnAccessorTest { - - lateinit var access: TypeConverterScopeColumnAccessor - - @Before - fun setup_converter() { - access = TypeConverterScopeColumnAccessor("global_typeConverterDateConverter") - } - - @Test - fun test_canGetConversion() { - assertEquals("global_typeConverterDateConverter.getModelValue(cursor.getString(\"name\"))", - access.set(CodeBlock.of("cursor.getString(\"name\")")).toString()) - } - - @Test - fun test_canSetConversion() { - assertEquals("global_typeConverterDateConverter.getDBValue(model.name)", - access.get(CodeBlock.of("model.name")).toString()) - } -} - -class EnumColumnAccessorTest { - - enum class TestEnum { - NAME - } - - @Test - fun test_canGetEnum() { - val access = EnumColumnAccessor(TypeName.get(TestEnum::class.java)) - assertEquals("candy.name()", access.get(CodeBlock.of("candy")).toString()) - } - - @Test - fun test_canSetEnum() { - val access = EnumColumnAccessor(TypeName.get(TestEnum::class.java)) - assertEquals("com.dbflow5.processor.test.EnumColumnAccessorTest.TestEnum.valueOf(model.test)", - access.set(CodeBlock.of("model.test")).toString()) - } -} - -class BlobColumnAccessorTest() { - - @Test - fun test_canGetBlob() { - val access = BlobColumnAccessor() - assertEquals("name.getBlob()", access.get(CodeBlock.of("name")).toString()) - } - - @Test - fun test_canSetBlob() { - val access = BlobColumnAccessor() - assertEquals("new com.dbflow5.Blob(cursor.getBlob(index))", - access.set(CodeBlock.of("cursor.getBlob(index)")).toString()) - } -} - -class BooleanTypeColumnAccessorTest() { - - @Test - fun test_canGetBoolean() { - val access = BooleanColumnAccessor() - assertEquals("model.isSet ? 1 : 0", access.get(CodeBlock.of("model.isSet")).toString()) - } - - @Test - fun test_canSetBoolean() { - val access = BooleanColumnAccessor() - assertEquals("cursor.getBoolean(index)", - access.set(CodeBlock.of("cursor.getBoolean(index)")).toString()) - } -} - -class CharColumnAccessorTest() { - @Test - fun test_canGetChar() { - val access = CharColumnAccessor() - assertEquals("new java.lang.String(new char[]{model.isSet})", access.get(CodeBlock.of("model.isSet")).toString()) - } - - @Test - fun test_canSetChar() { - val access = CharColumnAccessor() - assertEquals("cursor.getChar(index).charAt(0)", - access.set(CodeBlock.of("cursor.getChar(index)")).toString()) - } -} diff --git a/proguard-rules.pro b/proguard-rules.pro deleted file mode 100644 index bb65c6fe8879e5240c9b472d0e4826fdfc9e0b7c..0000000000000000000000000000000000000000 --- a/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/reactive-streams/.gitignore b/reactive-streams/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/reactive-streams/.gitignore @@ -0,0 +1 @@ +/build diff --git a/reactive-streams/build.gradle b/reactive-streams/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..0771fa75fc048a93a838ecb0c665d929494019b0 --- /dev/null +++ b/reactive-streams/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + api(project(":lib")) + api("io.reactivex.rxjava3:rxjava:3.0.4") +} diff --git a/reactive-streams/build.gradle.kts b/reactive-streams/build.gradle.kts deleted file mode 100644 index b1b2d490e59ff67bdec31685129fd500bdaf7339..0000000000000000000000000000000000000000 --- a/reactive-streams/build.gradle.kts +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.MinSdkRX) - targetSdkVersion(Versions.TargetSdk) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } - - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - api(project(":lib")) - api(Dependencies.RX) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/reactive-streams/consumer-rules.pro b/reactive-streams/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/reactive-streams/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/reactive-streams/gradle.properties b/reactive-streams/gradle.properties deleted file mode 100644 index 0781e3bf51ebd84ef99e736a87d080e847775fd9..0000000000000000000000000000000000000000 --- a/reactive-streams/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-rx2 -bt_packaging=aar -bt_artifact_id=dbflow-rx2 diff --git a/reactive-streams/proguard-rules.pro b/reactive-streams/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/reactive-streams/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/reactive-streams/src/main/AndroidManifest.xml b/reactive-streams/src/main/AndroidManifest.xml deleted file mode 100644 index 6c0b2d71b17e9a581d989b2606c907ee8f7e6d86..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/reactive-streams/src/main/config.json b/reactive-streams/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..73c544683579f9d64d6683e586c34ae4eef84d35 --- /dev/null +++ b/reactive-streams/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.reactivestreams", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "reactive-streams", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/CursorListFlowable.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/CursorListFlowable.java new file mode 100644 index 0000000000000000000000000000000000000000..f19cc04628f09b30f1b47c13d1daf1c25c68e702 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/CursorListFlowable.java @@ -0,0 +1,118 @@ +package com.dbflow5.reactivestreams.query; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowLog; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.query.list.FlowCursorIterator; +import com.dbflow5.query.list.FlowCursorList; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.transaction.Transaction; +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.SingleObserver; +import io.reactivex.rxjava3.disposables.Disposable; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +/** + * Description: Wraps a [ModelQueriable] into a [Flowable] that emits each item from the + * result of the [ModelQueriable] one at a time. + */ +public class CursorListFlowable extends Flowable { + + private final ModelQueriable modelQueriable; + private final DBFlowDatabase database; + + public CursorListFlowable(ModelQueriable modelQueriable, DBFlowDatabase database) { + super(); + this.modelQueriable = modelQueriable; + this.database = database; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new Subscription() { + private Transaction> transaction = null; + @Override + public void request(long n) { + Transaction.Builder> builder = database.beginTransactionAsync((Function>) databaseWrapper -> modelQueriable.cursorList(databaseWrapper)); + TransactionObservable.SingleTransaction> single = TransactionObservable.asSingle(builder); + transaction = single.transaction; + single.subscribe(new CursorResultObserver(subscriber, n)); + } + + @Override + public void cancel() { + if(transaction != null) { + transaction.cancel(); + } + } + }); + } + + public static class CursorResultObserver implements SingleObserver> { + private final Subscriber subscriber; + private final long count; + + private final AtomicLong emitted = new AtomicLong(); + private final AtomicLong requested = new AtomicLong(); + private Disposable disposable = null; + + public CursorResultObserver(Subscriber subscriber, long count) { + this.subscriber = subscriber; + this.count = count; + } + + @Override + public void onSubscribe(@NonNull Disposable disposable) { + this.disposable = disposable; + } + + @Override + public void onSuccess(@NonNull FlowCursorList ts) { + long starting; + if(this.count == Long.MAX_VALUE && requested.compareAndSet(0, Long.MAX_VALUE)){ + starting = 0; + }else { + starting = emitted.get(); + } + + long limit = this.count + starting; + + while (limit > 0) { + FlowCursorIterator iterator = ts.iterator(starting, limit); + try { + long i = 0; + while (disposable != null && !disposable.isDisposed() && iterator.hasNext() && i++ < limit) { + subscriber.onNext(iterator.next()); + } + emitted.addAndGet(i); + // no more items + if (disposable != null && !disposable.isDisposed() && i < limit) { + subscriber.onComplete(); + break; + } + limit = requested.addAndGet(-limit); + } catch (Exception e) { + FlowLog.logError(e); + subscriber.onError(e); + } finally { + try { + iterator.close(); + } catch (Exception e) { + FlowLog.logError(e); + subscriber.onError(e); + } + } + } + } + + @Override + public void onError(@NonNull Throwable e) { + subscriber.onError(e); + } + } +} diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..f1157c8d4d42e3ded0da19b118ecd8018f48e5c5 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.java @@ -0,0 +1,12 @@ +package com.dbflow5.reactivestreams.query; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.query.ModelQueriable; +import io.reactivex.rxjava3.core.Flowable; + +public class ModelQueriableExtensions { + public static Flowable queryStreamResults(ModelQueriable modelQueriable, DBFlowDatabase dbFlowDatabase) { + return new CursorListFlowable<>(modelQueriable, dbFlowDatabase); + } +} + diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.java new file mode 100644 index 0000000000000000000000000000000000000000..483d4be69fd73d3b53741278cc3c5fa831c24bb8 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.java @@ -0,0 +1,98 @@ +package com.dbflow5.reactivestreams.query; + +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.observing.OnTableChangedObserver; +import com.dbflow5.observing.TableObserver; +import com.dbflow5.query.From; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.reactivestreams.transaction.TransactionObservable; +import com.dbflow5.transaction.Transaction; +import io.reactivex.rxjava3.core.FlowableEmitter; +import io.reactivex.rxjava3.core.FlowableOnSubscribe; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.disposables.Disposable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Description: Emits when table changes occur for the related table on the [ModelQueriable]. + * If the [ModelQueriable] relates to a [Join], this can be multiple tables. + */ +public class TableChangeOnSubscribe implements FlowableOnSubscribe { + + private final ModelQueriable modelQueriable; + private final BiFunction, DatabaseWrapper, R> evalFn; + + public TableChangeOnSubscribe(ModelQueriable modelQueriable, BiFunction, DatabaseWrapper, R> evalFn) { + this.modelQueriable = modelQueriable; + this.evalFn = evalFn; + + onTableChangedObserver = new OnTableChangedObserver(new ArrayList<>(associatedTables())) { + @Override + protected void onChanged(Set> tables) { + if (!tables.isEmpty()) { + evaluateEmission(tables.stream().findFirst().get()); + } + } + }; + } + + private FlowableEmitter flowableEmitter; + + private final OnTableChangedObserver onTableChangedObserver; + + private final CompositeDisposable currentTransactions = new CompositeDisposable(); + + private Set> associatedTables() { + Set> classSet = null; + From from = modelQueriable.extractFrom(); + if(from != null){ + classSet = from.associatedTables(); + } + if(classSet == null){ + classSet = new HashSet<>(Collections.singleton(modelQueriable.table())); + } + return classSet; + } + + private void evaluateEmission(Class table) { + if(table == null) { + table = modelQueriable.table(); + } + if (this.flowableEmitter != null) { + Transaction.Builder builder = FlowManager.databaseForTable(table, dbFlowDatabase -> null) + .beginTransactionAsync((Function) databaseWrapper -> evalFn.apply(modelQueriable, databaseWrapper)) + .shouldRunInTransaction(false); + + Disposable disposable = TransactionObservable.asMaybe(builder).subscribe(r -> flowableEmitter.onNext(r)); + currentTransactions.add(disposable); + } + } + + public void subscribe(FlowableEmitter e) { + flowableEmitter = e; + + DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables().stream().findFirst().get()); + // force initialize the dbr + db.getWritableDatabase(); + + TableObserver observer = db.tableObserver(); + e.setDisposable(Disposable.fromRunnable(() -> { + observer.removeOnTableChangedObserver(onTableChangedObserver); + currentTransactions.dispose(); + })); + + observer.addOnTableChangedObserver(onTableChangedObserver); + + // emit once on subscribe. + evaluateEmission(modelQueriable.table()); + } + +} diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/BaseRXModel.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/BaseRXModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8e174193974eb0bfe649a009fc44a4ff2b876f05 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/BaseRXModel.java @@ -0,0 +1,63 @@ +package com.dbflow5.reactivestreams.structure; + +import com.dbflow5.database.DatabaseWrapper; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +/** + * Description: Similar to [BaseModel] with RX constructs. Extend this for convenience methods. + */ +public class BaseRXModel { + private RXModelAdapter rxModelAdapter() { + return new RXModelAdapter<>(BaseRXModel.class); + } + + public Single save(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().save(this, databaseWrapper); + } + + public Completable load(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().load(this, databaseWrapper); + } + + public Single delete(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().delete(this, databaseWrapper); + } + + public Single update(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().update(this, databaseWrapper); + } + + public Single insert(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().insert(this, databaseWrapper); + } + + public Single exists(DatabaseWrapper databaseWrapper) { + return rxModelAdapter().exists(this, databaseWrapper); + } + + public static Single rxSave(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).save(model, databaseWrapper); + } + + public static Completable rxLoad(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).load(model, databaseWrapper); + } + + public static Single rxDelete(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).delete(model, databaseWrapper); + } + + public static Single rxUpdate(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).update(model, databaseWrapper); + } + + public static Single rxInsert(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).insert(model, databaseWrapper); + } + + public static Single rxExists(T model, DatabaseWrapper databaseWrapper) { + return new RXModelAdapter<>((Class) model.getClass()).exists(model, databaseWrapper); + } +} + diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXModelAdapter.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXModelAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..cbfb30b98b230edbb95496bcad41d4ad631b0008 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXModelAdapter.java @@ -0,0 +1,77 @@ +package com.dbflow5.reactivestreams.structure; + +import com.dbflow5.adapter.ModelAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +import java.util.Collection; + +/** + * Description: Wraps most [ModelAdapter] modification operations into RX-style constructs. + */ +public class RXModelAdapter extends RXRetrievalAdapter { + private final ModelAdapter modelAdapter; + + public RXModelAdapter(ModelAdapter modelAdapter) { + super(modelAdapter); + this.modelAdapter = modelAdapter; + } + + public RXModelAdapter(Class table) { + this(FlowManager.modelAdapter(table)); + } + + public Single save(T model, DatabaseWrapper databaseWrapper) { + return Single.fromCallable(() -> modelAdapter.save(model, databaseWrapper)); + } + + public Completable saveAll(Collection models, DatabaseWrapper databaseWrapper) { + return Completable.fromCallable(() -> { + modelAdapter.saveAll(models, databaseWrapper); + return null; + }); + } + + public Single insert(T model, DatabaseWrapper databaseWrapper) { + return Single.fromCallable(() -> modelAdapter.insert(model, databaseWrapper)); + } + + public Completable insertAll(Collection models, DatabaseWrapper databaseWrapper) { + return Completable.fromCallable(() -> { + modelAdapter.insertAll(models, databaseWrapper); + return null; + }); + } + + public Single update(T model, DatabaseWrapper databaseWrapper) { + return Single.fromCallable(() -> modelAdapter.update(model, databaseWrapper)); + } + + public Completable updateAll(Collection models, DatabaseWrapper databaseWrapper) { + return Completable.fromCallable(() -> { + modelAdapter.updateAll(models, databaseWrapper); + return null; + }); + } + + public Single delete(T model, DatabaseWrapper databaseWrapper) { + return Single.fromCallable(() -> modelAdapter.delete(model, databaseWrapper)); + } + + public Completable deleteAll(Collection models, DatabaseWrapper databaseWrapper) { + return Completable.fromCallable(() -> { + modelAdapter.deleteAll(models, databaseWrapper); + return null; + }); + } + + public static RXModelAdapter from(ModelAdapter modelAdapter) { + return new RXModelAdapter<>(modelAdapter); + } + + public static RXModelAdapter from(Class table) { + return new RXModelAdapter<>(table); + } +} diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..13e72b5f890b03ff4967ff4a487b85efa776d6d9 --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.java @@ -0,0 +1,39 @@ +package com.dbflow5.reactivestreams.structure; + +import com.dbflow5.adapter.RetrievalAdapter; +import com.dbflow5.config.FlowManager; +import com.dbflow5.database.DatabaseWrapper; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +public class RXRetrievalAdapter{ + private final RetrievalAdapter retrievalAdapter; + + public RXRetrievalAdapter(RetrievalAdapter retrievalAdapter) { + this.retrievalAdapter = retrievalAdapter; + } + + public RXRetrievalAdapter(Class table) { + this(FlowManager.getRetrievalAdapter(table)); + } + + public Completable load(T model, DatabaseWrapper databaseWrapper) { + return Completable.fromCallable(() -> { + retrievalAdapter.load(model, databaseWrapper); + return null; + }); + } + + public Single exists(T model, DatabaseWrapper wrapper) { + return Single.fromCallable(() -> retrievalAdapter.exists(model, wrapper)); + } + + public static RXRetrievalAdapter from(RetrievalAdapter modelAdapter) { + return new RXRetrievalAdapter<>(modelAdapter); + } + + + public static RXRetrievalAdapter from(Class table) { + return new RXRetrievalAdapter<>(table); + } +} diff --git a/reactive-streams/src/main/java/com/dbflow5/reactivestreams/transaction/TransactionObservable.java b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/transaction/TransactionObservable.java new file mode 100644 index 0000000000000000000000000000000000000000..8f70dac2d4393bdfa59d9267124f5b4abbe3ca9e --- /dev/null +++ b/reactive-streams/src/main/java/com/dbflow5/reactivestreams/transaction/TransactionObservable.java @@ -0,0 +1,109 @@ +package com.dbflow5.reactivestreams.transaction; + +import com.dbflow5.database.DatabaseWrapper; +import com.dbflow5.query.ModelQueriable; +import com.dbflow5.reactivestreams.query.TableChangeOnSubscribe; +import com.dbflow5.transaction.Transaction; +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.MaybeObserver; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.core.SingleObserver; +import io.reactivex.rxjava3.disposables.Disposable; + +import java.util.function.BiFunction; + +public class TransactionObservable { + + public static MaybeTransaction asMaybe(Transaction.Builder builder) { + return new MaybeTransaction<>(builder); + } + + public static SingleTransaction asSingle(Transaction.Builder builder) { + return new SingleTransaction<>(builder); + } + + public static Flowable asFlowable(ModelQueriable modelQueriable, BiFunction, DatabaseWrapper, R> evalFn) { + return Flowable.create(new TableChangeOnSubscribe<>(modelQueriable, evalFn), BackpressureStrategy.LATEST); + } + + public static class TransactionDisposable implements Disposable { + private final Transaction transaction; + private boolean disposed = false; + + public TransactionDisposable(Transaction transaction) { + this.transaction = transaction; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void dispose() { + transaction.cancel(); + disposed = true; + } + } + + public static class SingleTransaction extends Single { + private final Transaction.Builder builder; + + public SingleTransaction(Transaction.Builder builder) { + super(); + this.builder = builder; + } + + public Transaction transaction = null; + + @Override + public void subscribeActual(SingleObserver observer) { + Transaction transaction = builder.success((rTransaction, r) -> { + observer.onSuccess(r); + return null; + }).error((rTransaction, throwable) -> { + observer.onError(throwable); + return null; + }).completion(rTransaction -> { + this.transaction = null; + return null; + }).build(); + + observer.onSubscribe(new TransactionDisposable(transaction)); + this.transaction = transaction; + transaction.execute(); + } + } + + public static class MaybeTransaction extends Maybe { + private final Transaction.Builder builder; + + public MaybeTransaction(Transaction.Builder builder) { + super(); + this.builder = builder; + } + + public Transaction transaction = null; + + @Override + public void subscribeActual(MaybeObserver observer) { + Transaction transaction = builder.success((rTransaction, r) -> { + observer.onSuccess(r); + return null; + }).completion(rTransaction -> { + this.transaction = null; + observer.onComplete(); + return null; + }).error((rTransaction, throwable) -> { + observer.onError(throwable); + return null; + }).build(); + + observer.onSubscribe(new TransactionDisposable(transaction)); + this.transaction = transaction; + transaction.execute(); + } + } +} \ No newline at end of file diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/CursorListFlowable.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/CursorListFlowable.kt deleted file mode 100644 index 2c2a30e75d99ff38ba859691b5f2cef4f9a8907c..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/CursorListFlowable.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.dbflow5.reactivestreams.query - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowLog -import com.dbflow5.query.ModelQueriable -import com.dbflow5.query.list.FlowCursorList -import com.dbflow5.reactivestreams.transaction.asSingle -import com.dbflow5.transaction.Transaction -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.SingleObserver -import io.reactivex.rxjava3.disposables.Disposable -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription -import java.util.concurrent.atomic.AtomicLong - -/** - * Description: Wraps a [ModelQueriable] into a [Flowable] that emits each item from the - * result of the [ModelQueriable] one at a time. - */ -class CursorListFlowable(private val modelQueriable: ModelQueriable, - private val database: DBFlowDatabase) - : Flowable() { - - override fun subscribeActual(subscriber: Subscriber) { - subscriber.onSubscribe(object : Subscription { - private var transaction: Transaction>? = null - - override fun request(n: Long) { - val single = database - .beginTransactionAsync { modelQueriable.cursorList(it) } - .asSingle() - transaction = single.transaction - single.subscribe(CursorResultObserver(subscriber, n)) - } - - override fun cancel() { - transaction?.cancel() - } - }) - } - - internal class CursorResultObserver( - private val subscriber: Subscriber, private val count: Long) - : SingleObserver> { - private val emitted: AtomicLong = AtomicLong() - private val requested: AtomicLong = AtomicLong() - private var disposable: Disposable? = null - - override fun onSubscribe(disposable: Disposable) { - this.disposable = disposable - } - - override fun onSuccess(ts: FlowCursorList) { - val starting = when { - this.count == Long.MAX_VALUE - && requested.compareAndSet(0, Long.MAX_VALUE) -> 0 - else -> emitted.toLong() - } - var limit = this.count + starting - - while (limit > 0) { - val iterator = ts.iterator(starting, limit) - try { - var i: Long = 0 - while (disposable?.isDisposed == false && iterator.hasNext() && i++ < limit) { - subscriber.onNext(iterator.next()) - } - emitted.addAndGet(i) - // no more items - if (disposable?.isDisposed == false && i < limit) { - subscriber.onComplete() - break - } - limit = requested.addAndGet(-limit) - } catch (e: Exception) { - FlowLog.logError(e) - subscriber.onError(e) - } finally { - try { - iterator.close() - } catch (e: Exception) { - FlowLog.logError(e) - subscriber.onError(e) - } - } - } - } - - override fun onError(e: Throwable) { - subscriber.onError(e) - } - - } -} diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.kt deleted file mode 100644 index 60a0d3f756564543e0503b6e27afe60b65365604..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/ModelQueriableExtensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -@file:JvmName("RXModelQueriable") - -package com.dbflow5.reactivestreams.query - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.query.ModelQueriable -import com.dbflow5.transaction.ITransactionQueue -import io.reactivex.rxjava3.core.Flowable - -/** - * Streams the results of this [ModelQueriable] through the [ITransactionQueue] and emitted one at - * time. - */ -fun ModelQueriable.queryStreamResults(dbFlowDatabase: DBFlowDatabase): Flowable = - CursorListFlowable(this, dbFlowDatabase) - diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.kt deleted file mode 100644 index c423468c7ed4e96fabb6c7565b3accc9f38b593a..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/query/TableChangeOnSubscribe.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.dbflow5.reactivestreams.query - -import com.dbflow5.config.FlowManager -import com.dbflow5.config.databaseForTable -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.observing.OnTableChangedObserver -import com.dbflow5.query.Join -import com.dbflow5.query.ModelQueriable -import com.dbflow5.query.extractFrom -import com.dbflow5.reactivestreams.transaction.asMaybe -import io.reactivex.rxjava3.core.FlowableEmitter -import io.reactivex.rxjava3.core.FlowableOnSubscribe -import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.disposables.Disposable -import kotlin.reflect.KClass - -/** - * Description: Emits when table changes occur for the related table on the [ModelQueriable]. - * If the [ModelQueriable] relates to a [Join], this can be multiple tables. - */ -class TableChangeOnSubscribe(private val modelQueriable: ModelQueriable, - private val evalFn: ModelQueriable.(DatabaseWrapper) -> R) - : FlowableOnSubscribe { - - private lateinit var flowableEmitter: FlowableEmitter - - private val currentTransactions = CompositeDisposable() - - private val associatedTables: Set> = modelQueriable.extractFrom()?.associatedTables - ?: setOf(modelQueriable.table) - - private val onTableChangedObserver = object : OnTableChangedObserver(associatedTables.toList()) { - override fun onChanged(tables: Set>) { - if (tables.isNotEmpty()) { - evaluateEmission(tables.first().kotlin) - } - } - } - - private fun evaluateEmission(table: KClass<*> = modelQueriable.table.kotlin) { - if (this::flowableEmitter.isInitialized) { - currentTransactions.add(databaseForTable(table) - .beginTransactionAsync { modelQueriable.evalFn(it) } - .shouldRunInTransaction(false) - .asMaybe() - .subscribe { - flowableEmitter.onNext(it) - }) - } - } - - @Suppress("UNCHECKED_CAST") - @Throws(Exception::class) - override fun subscribe(e: FlowableEmitter) { - flowableEmitter = e - - val db = FlowManager.getDatabaseForTable(associatedTables.first()) - // force initialize the dbr - db.writableDatabase - - val observer = db.tableObserver - e.setDisposable(Disposable.fromRunnable { - observer.removeOnTableChangedObserver(onTableChangedObserver) - currentTransactions.dispose() - }) - - observer.addOnTableChangedObserver(onTableChangedObserver) - - // emit once on subscribe. - evaluateEmission() - } - -} diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/BaseRXModel.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/BaseRXModel.kt deleted file mode 100644 index 9c323b878f7421a5484474771771d593cacc5677..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/BaseRXModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.dbflow5.reactivestreams.structure - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.annotation.ColumnIgnore -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.structure.BaseModel -import com.dbflow5.structure.InvalidDBConfiguration -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -/** - * Description: Similar to [BaseModel] with RX constructs. Extend this for convenience methods. - */ -open class BaseRXModel { - - /** - * @return The associated [ModelAdapter]. The [FlowManager] - * may throw a [InvalidDBConfiguration] for this call if this class - * is not associated with a table, so be careful when using this method. - */ - @delegate:ColumnIgnore - @delegate:Transient - private val rxModelAdapter: RXModelAdapter by lazy { RXModelAdapter(javaClass) } - - fun save(databaseWrapper: DatabaseWrapper): Single = - rxModelAdapter.save(this, databaseWrapper) - - fun load(databaseWrapper: DatabaseWrapper): Completable = - rxModelAdapter.load(this, databaseWrapper) - - fun delete(databaseWrapper: DatabaseWrapper): Single = - rxModelAdapter.delete(this, databaseWrapper) - - fun update(databaseWrapper: DatabaseWrapper): Single = - rxModelAdapter.update(this, databaseWrapper) - - fun insert(databaseWrapper: DatabaseWrapper): Single = - rxModelAdapter.insert(this, databaseWrapper) - - fun exists(databaseWrapper: DatabaseWrapper): Single = - rxModelAdapter.exists(this, databaseWrapper) -} - -fun T.rxSave(databaseWrapper: DatabaseWrapper): Single = - RXModelAdapter(javaClass).save(this, databaseWrapper) - -fun T.rxLoad(databaseWrapper: DatabaseWrapper): Completable = - RXModelAdapter(javaClass).load(this, databaseWrapper) - -fun T.rxDelete(databaseWrapper: DatabaseWrapper): Single = - RXModelAdapter(javaClass).delete(this, databaseWrapper) - -fun T.rxUpdate(databaseWrapper: DatabaseWrapper): Single = - RXModelAdapter(javaClass).update(this, databaseWrapper) - -fun T.rxInsert(databaseWrapper: DatabaseWrapper): Single = - RXModelAdapter(javaClass).insert(this, databaseWrapper) - -fun T.rxExists(databaseWrapper: DatabaseWrapper): Single = - RXModelAdapter(javaClass).exists(this, databaseWrapper) \ No newline at end of file diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXModelAdapter.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXModelAdapter.kt deleted file mode 100644 index 140c9d3661dffa699e29790d6ad0eec50093866d..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXModelAdapter.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.dbflow5.reactivestreams.structure - -import com.dbflow5.adapter.ModelAdapter -import com.dbflow5.config.modelAdapter -import com.dbflow5.database.DatabaseWrapper -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Completable.fromCallable -import io.reactivex.rxjava3.core.Single - -/** - * Description: Wraps most [ModelAdapter] modification operations into RX-style constructs. - */ -class RXModelAdapter internal constructor(private val modelAdapter: ModelAdapter) - : RXRetrievalAdapter(modelAdapter) { - - constructor(table: Class) : this(table.modelAdapter) - - fun save(model: T, databaseWrapper: DatabaseWrapper): Single = - Single.fromCallable { modelAdapter.save(model, databaseWrapper) } - - fun saveAll(models: Collection, databaseWrapper: DatabaseWrapper): Completable = - fromCallable { - modelAdapter.saveAll(models, databaseWrapper) - null - } - - fun insert(model: T, databaseWrapper: DatabaseWrapper): Single = - Single.fromCallable { modelAdapter.insert(model, databaseWrapper) } - - fun insertAll(models: Collection, - databaseWrapper: DatabaseWrapper): Completable = fromCallable { - modelAdapter.insertAll(models, databaseWrapper) - null - } - - fun update(model: T, databaseWrapper: DatabaseWrapper): Single = - Single.fromCallable { modelAdapter.update(model, databaseWrapper) } - - fun updateAll(models: Collection, databaseWrapper: DatabaseWrapper): Completable = - fromCallable { - modelAdapter.updateAll(models, databaseWrapper) - null - } - - fun delete(model: T, databaseWrapper: DatabaseWrapper): Single = - Single.fromCallable { modelAdapter.delete(model, databaseWrapper) } - - fun deleteAll(models: Collection, databaseWrapper: DatabaseWrapper): Completable = - fromCallable { - modelAdapter.deleteAll(models, databaseWrapper) - null - } - - companion object { - - @JvmStatic - fun from(modelAdapter: ModelAdapter): RXModelAdapter = - RXModelAdapter(modelAdapter) - - @JvmStatic - fun from(table: Class): RXModelAdapter = RXModelAdapter(table) - } -} diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.kt deleted file mode 100644 index 70de1ef8be8f227d6f37c3d34fd95d2f6a56f0cc..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/structure/RXRetrievalAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.dbflow5.reactivestreams.structure - -import com.dbflow5.adapter.RetrievalAdapter -import com.dbflow5.config.FlowManager -import com.dbflow5.database.DatabaseWrapper -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -/** - * Description: Mirrors the [RetrievalAdapter] with subset of exposed methods, mostly for - * [.load] and [.exists] - */ -open class RXRetrievalAdapter -internal constructor(private val retrievalAdapter: RetrievalAdapter) { - - internal constructor(table: Class) : this(FlowManager.getRetrievalAdapter(table)) - - fun load(model: T, databaseWrapper: DatabaseWrapper): Completable = Completable.fromCallable { - retrievalAdapter.load(model, databaseWrapper) - null - } - - /** - * @param model The model to query values from - * @return True if it exists as a row in the corresponding database table - */ - fun exists(model: T, wrapper: DatabaseWrapper): Single = - Single.fromCallable { retrievalAdapter.exists(model, wrapper) } - - companion object { - - @JvmStatic - fun from(modelAdapter: RetrievalAdapter): RXRetrievalAdapter = - RXRetrievalAdapter(modelAdapter) - - @JvmStatic - fun from(table: Class): RXRetrievalAdapter = RXRetrievalAdapter(table) - } -} diff --git a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/transaction/TransactionObservable.kt b/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/transaction/TransactionObservable.kt deleted file mode 100644 index e783b1d9f6b2f03b0b0ed16ba82ceed8e721ba09..0000000000000000000000000000000000000000 --- a/reactive-streams/src/main/kotlin/com/dbflow5/reactivestreams/transaction/TransactionObservable.kt +++ /dev/null @@ -1,95 +0,0 @@ -@file:JvmName("RXTransactions") - -package com.dbflow5.reactivestreams.transaction - -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.query.ModelQueriable -import com.dbflow5.reactivestreams.query.TableChangeOnSubscribe -import com.dbflow5.transaction.ITransactionQueue -import com.dbflow5.transaction.Transaction -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.MaybeObserver -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleObserver -import io.reactivex.rxjava3.disposables.Disposable - -/** - * Description: Returns a [Maybe] that executes the [this@beginMaybe] when called. - */ -fun Transaction.Builder.asMaybe(): MaybeTransaction = MaybeTransaction(this) - -/** - * Description: Returns a [Observable] that executes the [this@beginObservable] when called. - */ -fun Transaction.Builder.asSingle(): SingleTransaction = SingleTransaction(this) - -/** - * Observes any kind of table change from this [ModelQueriable], including individual model and global - * table changes. The passed [evalFn] is used to determine by you what to run and return on the subscribe - * of the [Flowable]. Use the passed [DatabaseWrapper] in your [ModelQueriable] statement. - * The [evalFn] runs on the [ITransactionQueue]. - */ -fun ModelQueriable.asFlowable( - evalFn: ModelQueriable.(DatabaseWrapper) -> R): Flowable = - Flowable.create(TableChangeOnSubscribe(this, evalFn), BackpressureStrategy.LATEST) - -open class TransactionDisposable(private val transaction: Transaction<*>) : Disposable { - private var disposed = false - - override fun isDisposed() = disposed - override fun dispose() { - transaction.cancel() - disposed = true - } -} - -/** - * Description: Wraps a [Transaction.Builder] in a transaction. Please note that the [Transaction.Builder] - * success will get consumed by the [Observer]. - */ -class SingleTransaction(private val builder: Transaction.Builder) - : Single() { - - /** - * The transaction on this [SingleObserver]. Will be null when not running. - */ - var transaction: Transaction? = null - - override fun subscribeActual(observer: SingleObserver) { - val transaction = builder.success { _, r -> observer.onSuccess(r) } - .error { _, throwable -> observer.onError(throwable) } - .completion { transaction = null } - .build() - observer.onSubscribe(TransactionDisposable(transaction)) - this.transaction = transaction - transaction.execute() - } -} - -/** - * Description: Wraps a [Transaction.Builder] in a transaction. Please note that the [Transaction.Builder] - * success will get consumed by the [Observer]. - */ -class MaybeTransaction(private val builder: Transaction.Builder) - : Maybe() { - - /** - * The transaction on this [SingleObserver]. Will be null when not running. - */ - var transaction: Transaction? = null - - override fun subscribeActual(observer: MaybeObserver) { - val transaction = builder.success { _, r -> observer.onSuccess(r) } - .completion { - transaction = null - observer.onComplete() - } - .error { _, throwable -> observer.onError(throwable) } - .build() - observer.onSubscribe(TransactionDisposable(transaction)) - this.transaction = transaction - transaction.execute() - } -} \ No newline at end of file diff --git a/reactive-streams/src/main/resources/base/element/string.json b/reactive-streams/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..a9d01f46a9760082071573448a04a2da311e2cd9 --- /dev/null +++ b/reactive-streams/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "reactivestreams_library", + "value": "reactivestreams_library" + } + ] +} diff --git a/reactive-streams/src/test/java/com/dbflow5/reactivestreams/ExampleTest.java b/reactive-streams/src/test/java/com/dbflow5/reactivestreams/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..43db40e7caa424135b8b940dcd11bd351b06b709 --- /dev/null +++ b/reactive-streams/src/test/java/com/dbflow5/reactivestreams/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.reactivestreams; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/settings.gradle b/settings.gradle index ee62f6e3b662f5703c730fc77332ddaddb5ea773..ca71466a422939a30ccedb94c2b7aae5340a4ffe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1 @@ -include ':lib', ':reactive-streams', ':coroutines', ':contentprovider', ':contentprovider-annotations', ':livedata', - ':processor', - ':core', - ':sqlcipher', - ':tests', - ':paging' - +include ':entry', ':lib', ':core', ':contentprovider-annotations', ':livedata', ':paging', ':reactive-streams', ':sqlcipher', ':contentprovider', ':processor' diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..bd77c4f9ef9e5b637ba6e39c18fc4315c0c050b6 --- /dev/null +++ b/sqlcipher/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' + + api(project(":lib")) +} diff --git a/sqlcipher/build.gradle.kts b/sqlcipher/build.gradle.kts deleted file mode 100644 index a5ecadf2d67e9252ad57fd4af36ee96a0f90f2d0..0000000000000000000000000000000000000000 --- a/sqlcipher/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} -// project.ext.artifactId = bt_name - -android { - compileSdkVersion(Versions.TargetSdk) - - defaultConfig { - minSdkVersion(Versions.SQLCipherMin) - targetSdkVersion(Versions.TargetSdk) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - api(Dependencies.SqlCipher) - api(project(":lib")) -} - -apply(from = "../kotlin-artifacts.gradle") diff --git a/sqlcipher/consumer-rules.pro b/sqlcipher/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/sqlcipher/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/sqlcipher/gradle.properties b/sqlcipher/gradle.properties deleted file mode 100644 index ecb42bd8c3eb4e9d1b62e1d61c5c4f4b7cdc8df9..0000000000000000000000000000000000000000 --- a/sqlcipher/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -bt_name=dbflow-sqlcipher -bt_packaging=aar -bt_artifact_id=dbflow-sqlcipher \ No newline at end of file diff --git a/sqlcipher/proguard-rules.pro b/sqlcipher/proguard-rules.pro index fb5e279715d9fb8ea60080253a8b42abb6ff26ad..f7666e47561d514b2a76d5a7dfbb43ede86da92a 100644 --- a/sqlcipher/proguard-rules.pro +++ b/sqlcipher/proguard-rules.pro @@ -1,17 +1 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/andrewgrosner/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/sqlcipher/settings.gradle b/sqlcipher/settings.gradle deleted file mode 100644 index 00126c83cd98c292680fc4b1685a6e5d292fb7c1..0000000000000000000000000000000000000000 --- a/sqlcipher/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = buildName \ No newline at end of file diff --git a/sqlcipher/src/main/AndroidManifest.xml b/sqlcipher/src/main/AndroidManifest.xml deleted file mode 100644 index 5f91eb09d95188d91ab08042c165de19bb5e60e1..0000000000000000000000000000000000000000 --- a/sqlcipher/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sqlcipher/src/main/config.json b/sqlcipher/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..88aa2b4cca0c4631992327dbb48c9fb381a8d341 --- /dev/null +++ b/sqlcipher/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.dbflow5.test", + "vendor": "dbflow5", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.dbflow5.sqlcipher", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "sqlcipher", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherDatabase.java b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..14a9629fd4ae682f4d480e0233753ff247c6f390 --- /dev/null +++ b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherDatabase.java @@ -0,0 +1,116 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.database.OhosDatabaseWrapper; +import com.dbflow5.database.DatabaseStatement; +import com.dbflow5.database.FlowCursor; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteException; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.ValuesBucket; + +import java.util.function.Function; + +/** + * Description: Implements the code necessary to use a [SQLiteDatabase] in dbflow. + */ +public class SQLCipherDatabase implements OhosDatabaseWrapper { + public SQLiteDatabase database; + + public SQLCipherDatabase(SQLiteDatabase database) { + this.database = database; + } + + @Override + public long updateWithOnConflict(String tableName, ValuesBucket contentValues, String[] whereColumn, String[] whereValues, RdbStore.ConflictResolution conflictAlgorithm) { + return 0; + } + + @Override + public long insertWithOnConflict(String tableName, String nullColumnHack, ValuesBucket values, RdbStore.ConflictResolution sqLiteDatabaseAlgorithmInt) { + return 0; + } + + @Override + public boolean isInTransaction() { + return database.inTransaction(); + } + + @Override + public int getVersion() { + return database.getVersion(); + } + + @Override + public void execSQL(String query) { + database.execSQL(query); + } + + @Override + public void beginTransaction() { + database.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + database.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + database.endTransaction(); + } + + @Override + public DatabaseStatement compileStatement(String rawQuery) { + return SQLCipherStatement.from(database.compileStatement(rawQuery)); + } + + @Override + public FlowCursor rawQuery(String query, String[] selectionArgs) { + return FlowCursor.from(database.rawQuery(query, selectionArgs)); + } + + @Override + public FlowCursor query(String tableName, String[] columns, String[] selection, String[] selectionArgs, String groupBy, String having, String orderBy) { + StringBuilder builder = new StringBuilder(); + for (String column : selection) { + if(builder.toString().length() > 0){ + builder.append(" AND "); + } + builder.append(column); + builder.append(" = ? "); + } + + return FlowCursor.from(database.query(tableName, columns, builder.toString(), selectionArgs, groupBy, having, orderBy)); + } + + @Override + public int delete(String tableName, String[] whereClause, String[] whereArgs) { + StringBuilder builder = new StringBuilder(); + for (String column : whereClause) { + if(builder.toString().length() > 0){ + builder.append(" AND "); + } + builder.append(column); + builder.append(" = ? "); + } + return database.delete(tableName, builder.toString(), whereArgs); + } + + public static SQLCipherDatabase from(SQLiteDatabase database) { + return new SQLCipherDatabase(database); + } + + public static com.dbflow5.database.SQLiteException toDBFlowSQLiteException(SQLiteException exception) { + return new com.dbflow5.database.SQLiteException("A Database Error Occurred", exception); + } + + public static T rethrowDBFlowException(Function fn) { + try { + return fn.apply(null); + } catch (SQLiteException e) { + throw toDBFlowSQLiteException(e); + } + } +} + diff --git a/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherOpenHelper.java b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherOpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..ef318d7572a3afb7624138ab71d42a21ced406db --- /dev/null +++ b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherOpenHelper.java @@ -0,0 +1,201 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.DatabaseFileUtils; +import com.dbflow5.config.DBFlowDatabase; +import com.dbflow5.config.DatabaseConfig; +import com.dbflow5.database.*; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteOpenHelper; +import ohos.app.Context; + +/** + * Description: The replacement [OpenHelper] for SQLCipher. Specify a subclass of this is [DatabaseConfig.getDatabaseClass] + * of your database to get it to work with specifying the secret you use for the databaseForTable. + */ +public class SQLCipherOpenHelper extends SQLiteOpenHelper implements OpenHelper { + + private final Context context; + + public LocalDatabaseHelperDelegate delegate; + private SQLCipherDatabase cipherDatabase = null; + private String _databaseName; + + public SQLCipherOpenHelper(Context context, DBFlowDatabase databaseDefinition, DatabaseCallback listener) { + super(context, databaseDefinition.isInMemory()? null : databaseDefinition.getDatabaseFileName(), null, databaseDefinition.databaseVersion()); + this.context = context; + _databaseName = databaseDefinition.getDatabaseFileName(); + + SQLiteDatabase.loadLibs(context); + if(delegate == null) { + delegate = new LocalDatabaseHelperDelegate(context, listener, databaseDefinition, databaseDefinition.backupEnabled()? + // Temp database mirrors existing + new BackupHelper(context, LocalDatabaseHelperDelegate.getTempDbFileName(databaseDefinition), databaseDefinition.databaseVersion(), databaseDefinition) : null); + } + } + + public LocalDatabaseHelperDelegate delegate() { + return delegate; + } + + @Override + public boolean isDatabaseIntegrityOk() { + return delegate.isDatabaseIntegrityOk(); + } + + @Override + public DatabaseWrapper database() { + if (cipherDatabase == null || !cipherDatabase.database.isOpen()) { + cipherDatabase = SQLCipherDatabase.from(getWritableDatabase(cipherSecret)); + } + return cipherDatabase; + } + + protected String cipherSecret; + + @Override + public void performRestoreFromBackup() { + delegate.performRestoreFromBackup(); + } + + @Override + public void backupDB() { + delegate.backupDB(); + } + + @Override + public void setDatabaseListener(DatabaseCallback callback) { + delegate.setDatabaseHelperListener(callback); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + delegate.onConfigure(SQLCipherDatabase.from(db)); + } + + @Override + public void onCreate(SQLiteDatabase db) { + delegate.onCreate(SQLCipherDatabase.from(db)); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + delegate.onUpgrade(SQLCipherDatabase.from(db), oldVersion, newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + delegate.onOpen(SQLCipherDatabase.from(db)); + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + if(cipherDatabase != null && cipherDatabase.database != null) { + if (enabled) { + cipherDatabase.database.enableWriteAheadLogging(); + } else { + cipherDatabase.database.disableWriteAheadLogging(); + } + } + } + + @Override + public void closeDB() { + database(); + if(cipherDatabase != null && cipherDatabase.database != null) { + cipherDatabase.database.close(); + } + } + + @Override + public void deleteDB() { + DatabaseFileUtils.deleteDatabase(context, _databaseName); + } + + /** + * Simple helper to manage backup. + */ + private class BackupHelper extends SQLiteOpenHelper implements OpenHelper { + private final Context context; + private SQLCipherDatabase sqlCipherDatabase = null; + private final LocalDatabaseHelper databaseHelper; + private final String _databaseName; + + public BackupHelper(Context context,String name, int version, DBFlowDatabase databaseDefinition) { + super(context, name, null, version); + this.context = context; + databaseHelper = new LocalDatabaseHelper(new OhosMigrationFileHelper(context), databaseDefinition); + _databaseName = databaseDefinition.getDatabaseFileName(); + } + + @Override + public DatabaseWrapper database() { + if (sqlCipherDatabase == null) { + sqlCipherDatabase = SQLCipherDatabase.from(getWritableDatabase(cipherSecret)); + } + return sqlCipherDatabase; + } + + @Override + public LocalDatabaseHelperDelegate delegate() { + return null; + } + + @Override + public boolean isDatabaseIntegrityOk() { + return false; + } + + @Override + public void performRestoreFromBackup() { + } + + @Override + public void backupDB() { + } + + @Override + public void closeDB() { + } + + @Override + public void setDatabaseListener(DatabaseCallback callback) { + } + + @Override + public void onConfigure(SQLiteDatabase db) { + databaseHelper.onConfigure(SQLCipherDatabase.from(db)); + } + + @Override + public void onCreate(SQLiteDatabase db) { + databaseHelper.onCreate(SQLCipherDatabase.from(db)); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + databaseHelper.onUpgrade(SQLCipherDatabase.from(db), oldVersion, newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + databaseHelper.onOpen(SQLCipherDatabase.from(db)); + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + } + + @Override + public void deleteDB() { + DatabaseFileUtils.deleteDatabase(context, _databaseName); + } + } + + public static DatabaseConfig.OpenHelperCreator createHelperCreator(Context context, String secret) { + return (db, callback) -> { + SQLCipherOpenHelper openHelper = new SQLCipherOpenHelper(context, db, callback); + openHelper.cipherSecret = secret; + return openHelper; + }; + } +} diff --git a/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherStatement.java b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..d3018bcc7cb8cfb5e1c48b68d40d19137d0fb737 --- /dev/null +++ b/sqlcipher/src/main/java/com/dbflow5/sqlcipher/SQLCipherStatement.java @@ -0,0 +1,85 @@ +package com.dbflow5.sqlcipher; + +import com.dbflow5.database.BaseDatabaseStatement; + +import net.sqlcipher.database.SQLiteStatement; + +/** + * Description: Implements the methods necessary for [DatabaseStatement]. Delegates calls to + * the contained [SQLiteStatement]. + */ +public class SQLCipherStatement extends BaseDatabaseStatement { + public SQLiteStatement statement; + + public SQLCipherStatement(SQLiteStatement statement) { + this.statement = statement; + } + + @Override + public long executeUpdateDelete() { + return SQLCipherDatabase.rethrowDBFlowException(unused -> (long)statement.executeUpdateDelete()); + } + + @Override + public void execute() { + statement.execute(); + } + + @Override + public void close() { + statement.close(); + } + + @Override + public long simpleQueryForLong() { + return SQLCipherDatabase.rethrowDBFlowException(unused -> statement.simpleQueryForLong()); + } + + @Override + public String simpleQueryForString() { + return SQLCipherDatabase.rethrowDBFlowException(unused -> statement.simpleQueryForString()); + } + + @Override + public long executeInsert() { + return SQLCipherDatabase.rethrowDBFlowException(unused -> statement.executeInsert()); + } + + @Override + public void bindString(int index, String s) { + statement.bindString(index, s); + } + + @Override + public void bindNull(int index) { + statement.bindNull(index); + } + + @Override + public void bindLong(int index, Long aLong) { + statement.bindLong(index, aLong); + } + + @Override + public void bindDouble(int index, Double aDouble) { + statement.bindDouble(index, aDouble); + } + + @Override + public void bindBlob(int index, byte[] bytes) { + statement.bindBlob(index, bytes); + } + + @Override + public void bindAllArgsAsStrings(String[] selectionArgs) { + if (selectionArgs != null) { + for (int i = selectionArgs.length; i > 0; i--) { + bindString(i, selectionArgs[i - 1]); + } + } + } + + public static SQLCipherStatement from(SQLiteStatement statement) { + return new SQLCipherStatement(statement); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java b/sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..c349b9313df2b58805aca12c532b4ffefebc6bfa --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java @@ -0,0 +1,673 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.data.rdb.DataObservable; +import ohos.data.rdb.DataObserver; +import ohos.data.resultset.ResultSet; +import ohos.data.resultset.SharedBlock; +import ohos.aafwk.ability.IDataAbilityObserver; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.utils.PacMap; +import ohos.utils.net.Uri; + + +/** + * This is an abstract cursor class that handles a lot of the common code + * that all cursors need to deal with and is provided for convenience reasons. + */ +public abstract class AbstractCursor implements ohos.data.resultset.SharedResultSet, ResultSet { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x002E,"AbstractCursor"); + + DataObservable mDataSetObservable = new DataObservable(); + // ContentObservable mContentObservable = new ContentObservable(); + + private PacMap mExtras = PacMap.EMPTY_PAC_MAP; + + /* -------------------------------------------------------- */ + /* These need to be implemented by subclasses */ + abstract public int getRowCount(); + + abstract public String[] getAllColumnNames(); + + abstract public String getString(int column); + abstract public short getShort(int column); + abstract public int getInt(int column); + abstract public long getLong(int column); + abstract public float getFloat(int column); + abstract public double getDouble(int column); + abstract public boolean isColumnNull(int column); + + abstract public ColumnType getColumnTypeForIndex(int column); + + // TODO implement getBlob in all cursor types + public byte[] getBlob(int column) { + throw new UnsupportedOperationException("getBlob is not supported"); + } + /* -------------------------------------------------------- */ + /* Methods that may optionally be implemented by subclasses */ + + /** + * returns a pre-filled window, return NULL if no such window + * @return SharedBlock + */ + public SharedBlock getBlock() { + return null; + } + + public int getColumnCount() { + return getAllColumnNames().length; + } + + public void deactivate() { + deactivateInternal(); + } + + /** + * deactivateInternal + * @hide + */ + public void deactivateInternal() { + if (mSelfObserver != null) { + mContentResolver.unregisterObserver(mNotifyUri, mSelfObserver); + mSelfObserverRegistered = false; + } + mDataSetObservable.notifyObservers(); //notifyInvalidated not available + } + + public boolean requery() { + if (mSelfObserver != null && mSelfObserverRegistered == false) { + + mContentResolver.registerObserver(mNotifyUri, mSelfObserver); + mSelfObserverRegistered = true; + } + mDataSetObservable.notifyObservers(); + return true; + } + + public boolean isClosed() { + return mClosed; + } + + public void close() { + mClosed = true; + // mContentObservable.unregisterAll(); //todo + deactivateInternal(); + } + + /** + * commitUpdates + * @hide + * @deprecated + * @param values value + * @return true/false + */ + public boolean commitUpdates(Map> values) { + return false; + } + + /** + * deleteRow + * @hide + * @deprecated + * @return true/false + */ + public boolean deleteRow() { + return false; + } + + /** + * This function is called every time the cursor is successfully scrolled + * to a new position, giving the subclass a chance to update any state it + * may have. If it returns false the move function will also do so and the + * cursor will scroll to the beforeFirst position. + * + * @param oldPosition the position that we're moving from + * @param newPosition the position that we're moving to + * @return true if the move is successful, false otherwise + */ + public boolean onGo(int oldPosition, int newPosition) { + return true; + } + + + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + // Default implementation, uses getString + String result = getString(columnIndex); + if (result != null) { + char[] data = buffer.data; + if (data == null || data.length < result.length()) { + buffer.data = result.toCharArray(); + } else { + result.getChars(0, result.length(), data, 0); + } + buffer.sizeCopied = result.length(); + } else { + buffer.sizeCopied = 0; + } + } + + /* -------------------------------------------------------- */ + /* Implementation */ + public AbstractCursor() { + mPos = -1; + mRowIdColumnIndex = -1; + mCurrentRowID = null; + mUpdatedRows = new HashMap>(); + } + + public final int getRowIndex() { + return mPos; + } + + public final boolean goToRow(int position) { + // Make sure position isn't past the end of the cursor + final int count = getRowCount(); + if (position >= count) { + mPos = count; + return false; + } + + // Make sure position isn't before the beginning of the cursor + if (position < 0) { + mPos = -1; + return false; + } + + // Check for no-op moves, and skip the rest of the work for them + if (position == mPos) { + return true; + } + + boolean result = onGo(mPos, position); + if (result == false) { + mPos = -1; + } else { + mPos = position; + if (mRowIdColumnIndex != -1) { + mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex)); + } + } + + return result; + } + + /** + * Copy data from cursor to CursorWindow + * @param position start position of data + * @param window + */ + public void fillBlock(int position, SharedBlock window) { + DatabaseUtils.cursorFillWindow(this, position, window); + } + + public final boolean goTo(int offset) { + return goToRow(mPos + offset); + } + + public final boolean goToFirstRow() { + return goToRow(0); + } + + public final boolean goToLastRow() { + return goToRow(getRowCount() - 1); + } + + public final boolean goToNextRow() { + return goToRow(mPos + 1); + } + + public final boolean goToPreviousRow() { + return goToRow(mPos - 1); + } + + public final boolean isAtFirstRow() { + return mPos == 0 && getRowCount() != 0; + } + + public final boolean isAtLastRow() { + int cnt = getRowCount(); + return mPos == (cnt - 1) && cnt != 0; + } + + public final boolean isStarted() { + if (getRowCount() == 0) { + return true; + } + return mPos == -1; + } + + public final boolean isEnded() { + if (getRowCount() == 0) { + return true; + } + return mPos == getRowCount(); + } + + public int getColumnIndexForName(String columnName) { + // Hack according to bug 903852 + final int periodIndex = columnName.lastIndexOf('.'); + if (periodIndex != -1) { + Exception e = new Exception(); + HiLog.error(label, "requesting column name with table name -- " + columnName, e); + columnName = columnName.substring(periodIndex + 1); + } + + String columnNames[] = getAllColumnNames(); + int length = columnNames.length; + for (int i = 0; i < length; i++) { + if (columnNames[i].equalsIgnoreCase(columnName)) { + return i; + } + } + + /*if (Config.LOGV) {*/ //todo + if (getRowCount() > 0) { + HiLog.warn(label,"AbstractCursor", "Unknown column " + columnName); + } + // + return -1; + } + + public String getColumnNameForIndex(int columnIndex) { + return getAllColumnNames()[columnIndex]; + } + + /** + * updateBlob + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateBlob(int columnIndex, byte[] value) { + return update(columnIndex, value); + } + + /** + * updateString + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateString(int columnIndex, String value) { + return update(columnIndex, value); + } + + /** + * updateShort + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateShort(int columnIndex, short value) { + return update(columnIndex, Short.valueOf(value)); + } + + /** + * updateInt + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateInt(int columnIndex, int value) { + return update(columnIndex, Integer.valueOf(value)); + } + + /** + * updateLong + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateLong(int columnIndex, long value) { + return update(columnIndex, Long.valueOf(value)); + } + + /** + * updateFloat + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateFloat(int columnIndex, float value) { + return update(columnIndex, Float.valueOf(value)); + } + + /** + * updateDouble + * @hide + * @deprecated + * @param columnIndex index + * @param value value + * @return true/false + */ + public boolean updateDouble(int columnIndex, double value) { + return update(columnIndex, Double.valueOf(value)); + } + + /** + * updateToNull + * @hide + * @deprecated + * @param columnIndex index + * @return true/false + */ + public boolean updateToNull(int columnIndex) { + return update(columnIndex, null); + } + + /** + * update + * @hide + * @deprecated + * @param columnIndex index + * @param obj object + * @return true/false + */ + public boolean update(int columnIndex, Object obj) { + if (!supportsUpdates()) { + return false; + } + + // Long.valueOf() returns null sometimes! +// Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); + Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); + if (rowid == null) { + throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex); + } + + synchronized(mUpdatedRows) { + Map row = mUpdatedRows.get(rowid); + if (row == null) { + row = new HashMap(); + mUpdatedRows.put(rowid, row); + } + row.put(getAllColumnNames()[columnIndex], obj); + } + + return true; + } + + /** + * Returns true if there are pending updates that have not yet been committed. + * + * @return true if there are pending updates that have not yet been committed. + * @hide + * @deprecated + */ + public boolean hasUpdates() { + synchronized(mUpdatedRows) { + return mUpdatedRows.size() > 0; + } + } + + /** + * abortUpdates + * @hide + * @deprecated + */ + public void abortUpdates() { + synchronized(mUpdatedRows) { + mUpdatedRows.clear(); + } + } + + /** + * commitUpdates + * @hide + * @deprecated + * @return true/false + */ + public boolean commitUpdates() { + return commitUpdates(null); + } + + /** + * supportsUpdates + * @hide + * @deprecated + * @return true/false + */ + public boolean supportsUpdates() { + return mRowIdColumnIndex != -1; + } + + public void registerObserver(DataObserver observer) { + // mContentObservable.registerObserver(observer); //todo contentobservable not available + } + + public void unregisterObserver(DataObserver observer) { + // cursor will unregister all observers when it close + if (!mClosed) { + // mContentObservable.unregisterObserver(observer);//todo + } + } + + /** + * This is hidden until the data set change model has been re-evaluated. + * @hide + */ + protected void notifyDataSetChange() { + mDataSetObservable.notifyObservers(); + } + + /** + * This is hidden until the data set change model has been re-evaluated. + * @hide + * @return DataObservable + */ + protected DataObservable getDataSetObservable() { + return mDataSetObservable; + + } + /*public void registerObserver(DataObserver observer) { //added instead of datasetobserver + // mDataSetObservable.registerObserver(observer); //todo + } + + public void unregisterObserver(DataObserver observer) { + // mDataSetObservable.unregisterObserver(observer);//todo + }*/ + + /** + * Subclasses must call this method when they finish committing updates to notify all + * observers. + * + * @param selfChange + */ + protected void onChange(boolean selfChange) { + synchronized (mSelfObserverLock) { + // mContentObservable.dispatchChange(selfChange); //todo + if (mNotifyUri != null && selfChange) { + mContentResolver.notifyChange(mNotifyUri); + } + } + } + + /** + * Specifies a content URI to watch for changes. + * + * @param cr The content resolver from the caller's context. + * @param notifyUri The URI to watch for changes. This can be a + * specific row URI, or a base URI for a whole class of content. + */ + @Override + public void setAffectedByUris(Object cr, List notifyUri) { + if (cr instanceof DataAbilityHelper) { + synchronized (mSelfObserverLock) { + mNotifyUriList = notifyUri; + for(Uri uri:notifyUri) { + mContentResolver = (DataAbilityHelper) cr; + if (mSelfObserver != null) { + mContentResolver.unregisterObserver(uri, mSelfObserver); + } + mSelfObserver = new SelfContentObserver(this); + mContentResolver.registerObserver(mNotifyUri, mSelfObserver); + mSelfObserverRegistered = true; + } + } + } + } + + public List getAffectedByUris() { + return mNotifyUriList; + } + + public boolean getWantsAllOnMoveCalls() { + return false; + } + + public void setExtensions(PacMap extras) { + mExtras = (extras == null) ? PacMap.EMPTY_PAC_MAP : extras; + } + + public PacMap getExtensions() { + return mExtras; + } //todo + + public PacMap respond(PacMap extras) { + return PacMap.EMPTY_PAC_MAP; + } // no such method in hmos + + /** + * This function returns true if the field has been updated and is + * used in conjunction with {@link #getUpdatedField} to allow subclasses to + * support reading uncommitted updates. NOTE: This function and + * {@link #getUpdatedField} should be called together inside of a + * block synchronized on mUpdatedRows. + * + * @param columnIndex the column index of the field to check + * @return true if the field has been updated, false otherwise + */ + protected boolean isFieldUpdated(int columnIndex) { + if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) { + Map updates = mUpdatedRows.get(mCurrentRowID); + if (updates != null && updates.containsKey(getAllColumnNames()[columnIndex])) { + return true; + } + } + return false; + } + + /** + * This function returns the uncommitted updated value for the field + * at columnIndex. NOTE: This function and {@link #isFieldUpdated} should + * be called together inside of a block synchronized on mUpdatedRows. + * + * @param columnIndex the column index of the field to retrieve + * @return the updated value + */ + protected Object getUpdatedField(int columnIndex) { + Map updates = mUpdatedRows.get(mCurrentRowID); + return updates.get(getAllColumnNames()[columnIndex]); + } + + /** + * This function throws CursorIndexOutOfBoundsException if + * the cursor position is out of bounds. Subclass implementations of + * the get functions should call this before attempting + * to retrieve data. + * + * @throws CursorIndexOutOfBoundsException + */ + protected void checkPosition() { + if (-1 == mPos || getRowCount() == mPos) { + throw new CursorIndexOutOfBoundsException(mPos, getRowCount()); + } + } + + @Override + protected void finalize() { + if (mSelfObserver != null && mSelfObserverRegistered == true) { + mContentResolver.unregisterObserver(mNotifyUri, mSelfObserver); + } + } + + /** + * Cursors use this class to track changes others make to their URI. + */ + protected static class SelfContentObserver implements IDataAbilityObserver { + WeakReference mCursor; + + public SelfContentObserver(AbstractCursor cursor) { + // super(null); //no superclass + mCursor = new WeakReference(cursor); + } + + /*@Override + public boolean deliverSelfNotifications() { + return false; + }*/ + + @Override + public void onChange() { + AbstractCursor cursor = mCursor.get(); + if (cursor != null) { + cursor.onChange(false); + } + } + + } + + /** + * This HashMap contains a mapping from Long rowIDs to another Map + * that maps from String column names to new values. A NULL value means to + * remove an existing value, and all numeric values are in their class + * forms, i.e. Integer, Long, Float, etc. + */ + protected HashMap> mUpdatedRows; + + /** + * This must be set to the index of the row ID column by any + * subclass that wishes to support updates. + */ + protected int mRowIdColumnIndex; + + protected int mPos; + + /** + * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of + * the column at {@link #mRowIdColumnIndex} for the current row this cursor is + * pointing at. + */ + protected Long mCurrentRowID; + protected DataAbilityHelper mContentResolver; + protected boolean mClosed = false; + private Uri mNotifyUri; + private List mNotifyUriList; + private IDataAbilityObserver mSelfObserver; + final private Object mSelfObserverLock = new Object(); + private boolean mSelfObserverRegistered; +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java b/sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..f6150a02cfd45b36998998de3eb97085e9ee6848 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import ohos.data.resultset.ResultSet; + +/** + * A base class for Cursors that store their data in {@link CursorWindow}s. + */ +public abstract class AbstractWindowedCursor extends AbstractCursor { + @Override + public byte[] getBlob(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + return (byte[])getUpdatedField(columnIndex); + } + } + return mWindow.getBlob(mPos, columnIndex); + } + + @Override + public String getString(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + return (String)getUpdatedField(columnIndex); + } + } + return mWindow.getString(mPos, columnIndex); + } + + @Override + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + super.copyStringToBuffer(columnIndex, buffer); + } + } + mWindow.copyStringToBuffer(mPos, columnIndex, buffer); + } + + @Override + public short getShort(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Number value = (Number)getUpdatedField(columnIndex); + return value.shortValue(); + } + } + return mWindow.getShort(mPos, columnIndex); + } + + @Override + public int getInt(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Number value = (Number)getUpdatedField(columnIndex); + return value.intValue(); + } + } + return mWindow.getInt(mPos, columnIndex); + } + + @Override + public long getLong(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Number value = (Number)getUpdatedField(columnIndex); + return value.longValue(); + } + } + return mWindow.getLong(mPos, columnIndex); + } + + @Override + public float getFloat(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Number value = (Number)getUpdatedField(columnIndex); + return value.floatValue(); + } + } + return mWindow.getFloat(mPos, columnIndex); + } + + @Override + public double getDouble(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Number value = (Number)getUpdatedField(columnIndex); + return value.doubleValue(); + } + } + return mWindow.getDouble(mPos, columnIndex); + } + + @Override + public boolean isColumnNull(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + return getUpdatedField(columnIndex) == null; + } + } + return mWindow.isNull(mPos, columnIndex); + } + + public boolean isBlob(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Object object = getUpdatedField(columnIndex); + return object == null || object instanceof byte[]; + } + } + return mWindow.isBlob(mPos, columnIndex); + } + + public boolean isString(int columnIndex) + { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Object object = getUpdatedField(columnIndex); + return object == null || object instanceof String; + } + } + return mWindow.isString(mPos, columnIndex); + } + + public boolean isLong(int columnIndex) { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Object object = getUpdatedField(columnIndex); + return object != null && (object instanceof Integer || object instanceof Long); + } + } + return mWindow.isLong(mPos, columnIndex); + } + + public boolean isFloat(int columnIndex) + { + checkPosition(); + synchronized(mUpdatedRows) { + if (isFieldUpdated(columnIndex)) { + Object object = getUpdatedField(columnIndex); + return object != null && (object instanceof Float || object instanceof Double); + } + } + return mWindow.isFloat(mPos, columnIndex); + } + + @Override + public ColumnType getColumnTypeForIndex(int columnIndex) { + checkPosition(); + return mWindow.getType(mPos, columnIndex); + } + + @Override + protected void checkPosition() { + super.checkPosition(); + if (mWindow == null) { + throw new StaleDataException("Access closed cursor"); + } + } + + @Override + public CursorWindow getBlock() { + return mWindow; + } + + /** + * Set a new cursor window to cursor, usually set a remote cursor window + * @param window cursor window + */ + public void setWindow(CursorWindow window) { + if (mWindow != null) { + mWindow.close(); + } + mWindow = window; + } + + public boolean hasWindow() { + return mWindow != null; + } + + /** + * This needs be updated in {@link #onMove} by subclasses, and + * needs to be set to NULL when the contents of the cursor change. + */ + protected CursorWindow mWindow; +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java b/sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java new file mode 100644 index 0000000000000000000000000000000000000000..1c80ce22f8c990e6911c901a25910b41601b3f46 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import ohos.rpc.IRemoteObject; +import ohos.rpc.MessageOption; +import ohos.rpc.MessageParcel; +import ohos.rpc.RemoteObject; +import ohos.utils.BasePacMap; +import ohos.utils.PacMap; + +import ohos.rpc.RemoteException; +import ohos.utils.Parcel; + +import java.util.HashMap; +import java.util.Map; + +/** + * Native implementation of the bulk cursor. This is only for use in implementing + * IPC, application code should use the Cursor interface. + * + * {@hide} + */ +public abstract class BulkCursorNative extends RemoteObject implements IBulkCursor +{ + public BulkCursorNative() + { + super(null); + attachLocalInterface(this, descriptor); + } + + /** + * Cast a Binder object into a content resolver interface, generating + * a proxy if needed. + * @param obj IRemoteObject + * @return IBulkCursor + */ + static public IBulkCursor asInterface(IRemoteObject obj) + { + if (obj == null) { + return null; + } + IBulkCursor in = (IBulkCursor)obj.queryLocalInterface(descriptor); + if (in != null) { + return in; + } + + return new BulkCursorProxy(obj); + } + + @Override + public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption flags) + throws RemoteException { + try { + switch (code) { + case GET_CURSOR_WINDOW_TRANSACTION: { + data.readInterfaceToken(); + int startPos = data.readInt(); + CursorWindow window = getWindow(startPos); + if (window == null) { + reply.writeInt(0); + return true; + } + reply.writeNoException(); + reply.writeInt(1); + window.marshalling(reply, 0); + return true; + } + + case COUNT_TRANSACTION: { + data.readInterfaceToken(); + int count = count(); + reply.writeNoException(); + reply.writeInt(count); + return true; + } + + case GET_COLUMN_NAMES_TRANSACTION: { + data.readInterfaceToken(); + String[] columnNames = getColumnNames(); + reply.writeNoException(); + reply.writeInt(columnNames.length); + int length = columnNames.length; + for (int i = 0; i < length; i++) { + reply.writeString(columnNames[i]); + } + return true; + } + + case DEACTIVATE_TRANSACTION: { + data.readInterfaceToken(); + deactivate(); + reply.writeNoException(); + return true; + } + + case CLOSE_TRANSACTION: { + data.readInterfaceToken(); + close(); + reply.writeNoException(); + return true; + } + + case REQUERY_TRANSACTION: { + data.readInterfaceToken(); + /* IContentObserver observer = + IContentObserver.Stub.asInterface(data.readRemoteObject()); + CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); + int count = requery(observer, window); + reply.writeNoException(); + reply.writeInt(count); // TODO AIDL + reply.writeParcelable(getExtras());*/ + return true; + } + + case UPDATE_ROWS_TRANSACTION: { + data.readInterfaceToken(); + // TODO - what ClassLoader should be passed to readHashMap? + // TODO - switch to Bundle + HashMap> values = (HashMap>)data.readMap(); + boolean result = updateRows(values); + reply.writeNoException(); + reply.writeInt((result == true ? 1 : 0)); + return true; + } + + case DELETE_ROW_TRANSACTION: { + data.readInterfaceToken(); + int position = data.readInt(); + boolean result = deleteRow(position); + reply.writeNoException(); + reply.writeInt((result == true ? 1 : 0)); + return true; + } + + case ON_MOVE_TRANSACTION: { + data.readInterfaceToken(); + int position = data.readInt(); + onMove(position); + reply.writeNoException(); + return true; + } + + case WANTS_ON_MOVE_TRANSACTION: { + data.readInterfaceToken(); + boolean result = getWantsAllOnMoveCalls(); + reply.writeNoException(); + reply.writeInt(result ? 1 : 0); + return true; + } + + case GET_EXTRAS_TRANSACTION: { + data.readInterfaceToken(); + PacMap extras = getExtras(); + reply.writeNoException(); + reply.writeSequenceable(extras); + return true; + } + + case RESPOND_TRANSACTION: { + data.readInterfaceToken(); + PacMap extras = data.createSequenceable(getClass().getClassLoader()); + PacMap returnExtras = respond(extras); + reply.writeNoException(); + reply.writeSequenceable(returnExtras); + return true; + } + /*default: + throw new IllegalStateException("Unexpected value: " + code);*/ + } + } catch (Exception e) { + DatabaseUtils.writeExceptionToParcel(reply, e); + return true; + } + return super.onRemoteRequest(code, data, reply, flags); //TODO today + } + + public IRemoteObject asObject() + { + return this; + } +} + +final class BulkCursorProxy implements IBulkCursor { + private IRemoteObject mRemote; + private PacMap mExtras; + + public BulkCursorProxy(IRemoteObject remote) + { + mRemote = remote; + mExtras = null; + } + + public IRemoteObject asObject() + { + return mRemote; + } + + public CursorWindow getWindow(int startPos) throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + data.writeInt(startPos); + + mRemote.sendRequest(GET_CURSOR_WINDOW_TRANSACTION, (MessageParcel) data, (MessageParcel)reply, new MessageOption(0));//todo + + DatabaseUtils.readExceptionFromParcel(reply); //todo + + CursorWindow window = null; + if (reply.readInt() == 1) { + window = CursorWindow.createFromParcel(reply); + } + + data.reclaim(); + reply.reclaim(); + + return window; + } + + public void onMove(int position) throws RemoteException { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + data.writeInt(position); + + mRemote.sendRequest(ON_MOVE_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + data.reclaim(); + reply.reclaim(); + } + + public int count() throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + boolean result = mRemote.sendRequest(COUNT_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + int count; + if (result == false) { + count = -1; + } else { + count = reply.readInt(); + } + data.reclaim(); + reply.reclaim(); + return count; + } + + public String[] getColumnNames() throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + mRemote.sendRequest(GET_COLUMN_NAMES_TRANSACTION, (MessageParcel)data,(MessageParcel) reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + String[] columnNames = null; + int numColumns = reply.readInt(); + columnNames = new String[numColumns]; + for (int i = 0; i < numColumns; i++) { + columnNames[i] = reply.readString(); + } + + data.reclaim(); + reply.reclaim(); + return columnNames; + } + + public void deactivate() throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + mRemote.sendRequest(DEACTIVATE_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + DatabaseUtils.readExceptionFromParcel(reply); + + data.reclaim(); + reply.reclaim(); + } + + public void close() throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + mRemote.sendRequest(CLOSE_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + DatabaseUtils.readExceptionFromParcel(reply); + + data.reclaim(); + reply.reclaim(); + } + + /*public int requery(IContentObserver observer, CursorWindow window) throws RemoteException { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + // ((MessageParcel)data).writeRemoteObject(observer);//todo AIDL + window.marshalling(data, 0); + + boolean result = mRemote.sendRequest(REQUERY_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + int count; + if (!result) { + count = -1; + } else { + count = reply.readInt(); + mExtras = reply.createSequenceable(getClass().getClassLoader()); + } + + data.reclaim(); + reply.reclaim(); + + return count; + }*/ + + public boolean updateRows(Map values) throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + data.writeMap(values); + + mRemote.sendRequest(UPDATE_ROWS_TRANSACTION, (MessageParcel)data,(MessageParcel) reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + boolean result = (reply.readInt() == 1); + + data.reclaim(); + reply.reclaim(); + + return result; + } + + public boolean deleteRow(int position) throws RemoteException + { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + data.writeInt(position); + + mRemote.sendRequest(DELETE_ROW_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + boolean result = (reply.readInt() == 1 ? true : false); + + data.reclaim(); + reply.reclaim(); + + return result; + } + + public boolean getWantsAllOnMoveCalls() throws RemoteException { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + mRemote.sendRequest(WANTS_ON_MOVE_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + int result = reply.readInt(); + data.reclaim(); + reply.reclaim(); + return result != 0; + } + + public PacMap getExtras() throws RemoteException { + if (mExtras == null) { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + mRemote.sendRequest(GET_EXTRAS_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + reply.addAppClassLoader(ClassLoader.getSystemClassLoader()); + mExtras = reply.createSequenceable(getClass().getClassLoader()); + data.reclaim(); + reply.reclaim(); + } + return mExtras; + } + + public PacMap respond(PacMap extras) throws RemoteException { + Parcel data = Parcel.create(); + Parcel reply = Parcel.create(); + + ((MessageParcel)data).writeInterfaceToken(descriptor); + + data.writeSequenceable(extras); + + mRemote.sendRequest(RESPOND_TRANSACTION, (MessageParcel)data, (MessageParcel)reply, new MessageOption(0)); + + DatabaseUtils.readExceptionFromParcel(reply); + + PacMap returnExtras = reply.createSequenceable(getClass().getClassLoader()); + data.reclaim(); + reply.reclaim(); + return returnExtras; + } +} + + diff --git a/sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java b/sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java new file mode 100644 index 0000000000000000000000000000000000000000..874e040ba76748053ac142b2b41385109d3ef309 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import java.util.Map; + +import ohos.data.rdb.DataObserver; +import ohos.hiviewdfx.HiLogLabel; +import ohos.utils.PacMap; +import ohos.rpc.RemoteException; +import ohos.hiviewdfx.HiLog; + +/** + * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local + * process. + * + * {@hide} + */ +public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { + static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00203, "BulkCursorToCursorAdaptor"); + + private SelfContentObserver mObserverBridge; + private IBulkCursor mBulkCursor; + private int mCount; + private String[] mColumns; + private boolean mWantsAllOnMoveCalls; + + public void set(IBulkCursor bulkCursor) { + mBulkCursor = bulkCursor; + + try { + mCount = mBulkCursor.count(); + mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls(); + + // Search for the rowID column index and set it for our parent + mColumns = mBulkCursor.getColumnNames(); + mRowIdColumnIndex = findRowIdColumnIndex(mColumns); + } catch (RemoteException ex) { + HiLog.error(label, "Setup failed because the remote process is dead"); + } + } + + /** + * Version of set() that does fewer Binder calls if the caller + * already knows BulkCursorToCursorAdaptor's properties. + * @param bulkCursor bulkCursor + * @param count count + * @param idIndex index + */ + public void set(IBulkCursor bulkCursor, int count, int idIndex) { + mBulkCursor = bulkCursor; + mColumns = null; // lazily retrieved + mCount = count; + mRowIdColumnIndex = idIndex; + } + + /** + * Returns column index of "_id" column, or -1 if not found. + * @param columnNames columnNames + * @return index + */ + public static int findRowIdColumnIndex(String[] columnNames) { + int length = columnNames.length; + for (int i = 0; i < length; i++) { + if (columnNames[i].equals("_id")) { + return i; + } + } + return -1; + } + + /** + * Gets a SelfDataChangeOberserver that can be sent to a remote + * process to receive change notifications over IPC. + * + * @return A SelfContentObserver hooked up to this Cursor + */ + /* public synchronized IContentObserver getObserver() { + if (mObserverBridge == null) { + mObserverBridge = new SelfContentObserver(this); + } + return null;//mObserverBridge.getContentObserver(); //TODO nf fix this + }*/ + + @Override + public int getRowCount() { + return mCount; + } + + @Override + public boolean onGo(int oldPosition, int newPosition) { + try { + // Make sure we have the proper window + if (mWindow != null) { + if (newPosition < mWindow.getStartRowIndex() || + newPosition >= (mWindow.getStartRowIndex() + mWindow.getRowCount())) { + mWindow = mBulkCursor.getWindow(newPosition); + } else if (mWantsAllOnMoveCalls) { + mBulkCursor.onMove(newPosition); + } + } else { + mWindow = mBulkCursor.getWindow(newPosition); + } + } catch (RemoteException ex) { + // We tried to get a window and failed + HiLog.error(label, "Unable to get window because the remote process is dead"); + return false; + } + + // Couldn't obtain a window, something is wrong + if (mWindow == null) { + return false; + } + + return true; + } + + @Override + public void deactivate() { + // This will call onInvalidated(), so make sure to do it before calling release, + // which is what actually makes the data set invalid. + super.deactivate(); + + try { + mBulkCursor.deactivate(); + } catch (RemoteException ex) { + HiLog.warn(label, "Remote process exception when deactivating"); + } + mWindow = null; + } + + @Override + public void close() { + super.close(); + try { + mBulkCursor.close(); + } catch (RemoteException ex) { + HiLog.warn(label, "Remote process exception when closing"); + } + mWindow = null; + } + + @Override + public boolean requery() { + try { + int oldCount = mCount; + //TODO get the window from a pool somewhere to avoid creating the memory dealer + /*mCount = mBulkCursor.requery(getObserver(), new CursorWindow( + false *//* the window will be accessed across processes *//*));*/ //todo + if (mCount != -1) { + mPos = -1; + mWindow = null; + + // super.requery() will call onChanged. Do it here instead of relying on the + // observer from the far side so that observers can see a correct value for mCount + // when responding to onChanged. + super.requery(); + return true; + } else { + deactivate(); + return false; + } + } catch (Exception ex) { + HiLog.error(label, "Unable to requery because the remote process exception ",ex.getMessage()); + deactivate(); + return false; + } + } + + /** + * deleteRow + * @hide + * @deprecated + * @return true/false + */ + @Override + public boolean deleteRow() { + try { + boolean result = mBulkCursor.deleteRow(mPos); + if (result != false) { + // The window contains the old value, discard it + mWindow = null; + + // Fix up the position + mCount = mBulkCursor.count(); + if (mPos < mCount) { + int oldPos = mPos; + mPos = -1; + goToRow(oldPos); + } else { + mPos = mCount; + } + + // Send the change notification + onChange(true); + } + return result; + } catch (RemoteException ex) { + HiLog.error(label, "Unable to delete row because the remote process is dead"); + return false; + } + } + + @Override + public String[] getAllColumnNames() { + if (mColumns == null) { + try { + mColumns = mBulkCursor.getColumnNames(); + } catch (RemoteException ex) { + HiLog.error(label, "Unable to fetch column names because the remote process is dead"); + return null; + } + } + return mColumns; + } + + /** + * commitUpdates + * @hide + * @deprecated + * @param additionalValues additionalValues + * @return true/false + */ + @Override + public boolean commitUpdates(Map> additionalValues) { + if (!supportsUpdates()) { + HiLog.error(label, "commitUpdates not supported on this cursor, did you include the _id column?"); + return false; + } + + synchronized(mUpdatedRows) { + if (additionalValues != null) { + mUpdatedRows.putAll(additionalValues); + } + + if (mUpdatedRows.size() <= 0) { + return false; + } + + try { + boolean result = mBulkCursor.updateRows(mUpdatedRows); + + if (result == true) { + mUpdatedRows.clear(); + + // Send the change notification + onChange(true); + } + return result; + } catch (RemoteException ex) { + HiLog.error(label, "Unable to commit updates because the remote process is dead"); + return false; + } + } + } + + @Override + public PacMap getExtensions() { + try { + return mBulkCursor.getExtras(); + } catch (RemoteException e) { + // This should never happen because the system kills processes that are using remote + // cursors when the provider process is killed. + throw new RuntimeException(e); + } + } + + @Override + public PacMap respond(PacMap extras) { + try { + return mBulkCursor.respond(extras); + } catch (RemoteException e) { + // the system kills processes that are using remote cursors when the provider process + // is killed, but this can still happen if this is being called from the system process, + // so, better to log and return an empty bundle. + HiLog.warn(label, "respond() threw RemoteException, returning an empty bundle.", e); + return PacMap.EMPTY_PAC_MAP; + } + } + + @Override + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + // TODO Auto-generated method stub + + } + + @Override + public void registerObserver(DataObserver observer) { + // TODO Auto-generated method stub + + } + +/* @Override + public void registerdataSetObserver(DataSetObserver observer) { { //todo no such method in hmos abstractcursor + // TODO Auto-generated method stub + + }*/ + + @Override + public void unregisterObserver(DataObserver observer) { + // TODO Auto-generated method stub + + } + +/*// @Override + public void unregisterDataSetObserver(DataSetObserver observer) { //todo no such method in hmos abstractcursor + // TODO Auto-generated method stub + + }*/ + + + + +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/CharArrayBuffer.java b/sqlcipher/src/main/java/net/sqlcipher/CharArrayBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..f5a2359ceef2127961db2a7be48bc235542f0b55 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CharArrayBuffer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +/** + * This is used for {@link Cursor#copyStringToBuffer} + */ +public final class CharArrayBuffer { + public CharArrayBuffer(int size) { + data = new char[size]; + } + + public CharArrayBuffer(char[] buf) { + data = buf; + } + + public char[] data; // In and out parameter + public int sizeCopied; // Out parameter +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/CrossProcessCursorWrapper.java b/sqlcipher/src/main/java/net/sqlcipher/CrossProcessCursorWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..7e439e278ea034f82982c30982710101d4b8e369 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CrossProcessCursorWrapper.java @@ -0,0 +1,28 @@ +package net.sqlcipher; + +import ohos.data.resultset.ResultSet; +import ohos.data.resultset.SharedBlock; +import ohos.data.resultset.SharedResultSet; + +public class CrossProcessCursorWrapper extends CursorWrapper implements SharedResultSet { + + public CrossProcessCursorWrapper(ResultSet cursor) { + super(cursor); + } + + @Override + public SharedBlock getBlock() { + return null; + } + + @Override + public void fillBlock(int position, SharedBlock window) { + DatabaseUtils.cursorFillWindow(this, position, window); + } + + @Override + public boolean onGo(int oldPosition, int newPosition) { + return true; + } +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java b/sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java new file mode 100644 index 0000000000000000000000000000000000000000..71bc8a3195c8f9cc0b004f544f785e4ff03f6def --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +/** + * An exception indicating that a cursor is out of bounds. + */ +public class CursorIndexOutOfBoundsException extends IndexOutOfBoundsException { + + public CursorIndexOutOfBoundsException(int index, int size) { + super("Index " + index + " requested, with a size of " + size); + } + + public CursorIndexOutOfBoundsException(String message) { + super(message); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java b/sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..3a813e26bf137dca65616e7f6471a4fa649bc6cd --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import net.sqlcipher.CharArrayBuffer; +import ohos.data.resultset.ResultSet; +import ohos.rpc.IRemoteObject; +import ohos.rpc.MessageParcel; +import ohos.utils.Parcel; +import ohos.utils.Sequenceable; + +/** + * A buffer containing multiple cursor rows. + */ +public class CursorWindow extends ohos.data.resultset.SharedBlock implements Sequenceable { + private long nWindow; + private int mStartPos; + private int mRequiredPos; + CursorWindowHelper cursorWindowHelper = new CursorWindowHelper(); + private static CursorWindowAllocation allocation = new DefaultCursorWindowAllocation(); + + public static void setCursorWindowAllocation(CursorWindowAllocation value){ + allocation = value; + } + public static CursorWindowAllocation getCursorWindowAllocation() { + return allocation; + } + + /** + * Creates a new empty window. + * + * @param localWindow true if this window will be used in this process only + */ + public CursorWindow(boolean localWindow) { + super(""); // todo no super constructor taking in boolean value + mStartPos = 0; + if(allocation == null){ + allocation = new DefaultCursorWindowAllocation(); + } + cursorWindowHelper = new CursorWindowHelper(); + native_init(localWindow, + allocation.getInitialAllocationSize(), + allocation.getGrowthPaddingSize(), + allocation.getMaxAllocationSize()); + } + /** + * Returns the starting position of this window within the entire + * Cursor's result set. + * + * @return the starting position of this window within the entire + * Cursor's result set. + */ + public int getStartRowIndex() { + return mStartPos; + } + + /** + * Set the start position of cursor window + * @param pos + */ + public void setStartRowIndex(int pos) { + mStartPos = pos; + } + + public int getRequiredPosition(){ + return mRequiredPos; + } + + public void setRequiredPosition(int pos) { + mRequiredPos = pos; + } + + /** + * Returns the number of rows in this window. + * + * @return the number of rows in this window. + */ + public int getRowCount() { + cursorWindowHelper.acquireReference(); + try { + return getNumRows_native(); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native int getNumRows_native(); + /** + * Set number of Columns + * @param columnNum + * @return true if success + */ + public boolean setColumnCount(int columnNum) { + cursorWindowHelper.acquireReference(); + try { + return setNumColumns_native(columnNum); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean setNumColumns_native(int columnNum); + + /** + * Allocate a row in cursor window + * @return false if cursor window is out of memory + */ + public boolean allocateRow(){ + cursorWindowHelper.acquireReference(); + try { + return allocRow_native(); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean allocRow_native(); + + /** + * Free the last row + */ + public void freeLastRow(){ + cursorWindowHelper.acquireReference(); + try { + freeLastRow_native(); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native void freeLastRow_native(); + + /** + * copy byte array to cursor window + * @param value + * @param row + * @param col + * @return false if fail to copy + */ + public boolean putBlob(int row, int col, byte[] value) { + cursorWindowHelper.acquireReference(); + try { + return putBlob_native(value, row - mStartPos, col); // todo native methods needs to check format + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean putBlob_native(byte[] value, int row, int col); + + /** + * Copy String to cursor window + * @param value + * @param row + * @param col + * @return false if fail to copy + */ + public boolean putString(int row, int col, String value) { + cursorWindowHelper.acquireReference(); + try { + return putString_native(value, row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean putString_native(String value, int row, int col); + + /** + * Copy integer to cursor window + * @param value + * @param row + * @param col + * @return false if fail to copy + */ + public boolean putLong(int row, int col, long value) { + cursorWindowHelper.acquireReference(); + try { + return putLong_native(value, row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean putLong_native(long value, int row, int col); + + + /** + * Copy double to cursor window + * @param value + * @param row + * @param col + * @return false if fail to copy + */ + public boolean putDouble(int row, int col, double value) { + cursorWindowHelper.acquireReference(); + try { + return putDouble_native(value, row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean putDouble_native(double value, int row, int col); + + /** + * Set the [row, col] value to NULL + * @param row + * @param col + * @return false if fail to copy + */ + public boolean putNull(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return putNull_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean putNull_native(int row, int col); + + + /** + * Returns {@code true} if given field is {@code NULL}. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return {@code true} if given field is {@code NULL} + */ + public boolean isNull(int row, int col) { //deprecated + cursorWindowHelper.acquireReference(); + try { + return isNull_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean isNull_native(int row, int col); + + /** + * Returns a byte array for the given field. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a String value for the given field + */ + public byte[] getBlob(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return getBlob_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns the value at (row, col) as a byte array. + * + *

If the value is null, then null is returned. If the + * type of column col is a string type, then the result + * is the array of bytes that make up the internal representation of the + * string value. If the type of column col is integral or floating-point, + * then an {@link SQLiteException} is thrown. + * + * @param row row + * @param col column + * @return byte[] + */ + private native byte[] getBlob_native(int row, int col); + + /** + * Returns data type of the given column's value. + *

+ * Returned column types are + *

    + *
  • {@link Cursor#FIELD_TYPE_NULL}
  • + *
  • {@link Cursor#FIELD_TYPE_INTEGER}
  • + *
  • {@link Cursor#FIELD_TYPE_FLOAT}
  • + *
  • {@link Cursor#FIELD_TYPE_STRING}
  • + *
  • {@link Cursor#FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return the value type + */ + public ResultSet.ColumnType getType(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + int value = getType_native(row - mStartPos, col); + return getColumnTypefromNative(value); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + public ResultSet.ColumnType getColumnTypefromNative(int value) { + if (value == 0) { + return ResultSet.ColumnType.TYPE_NULL; + } else if (value == 1) { + return ResultSet.ColumnType.TYPE_INTEGER; + } else if (value == 2) { + return ResultSet.ColumnType.TYPE_FLOAT; + } else if (value == 3) { + return ResultSet.ColumnType.TYPE_STRING; + } else { + return ResultSet.ColumnType.TYPE_BLOB; + } + } + + /** + * Checks if a field contains either a blob or is null. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return {@code true} if given field is {@code NULL} or a blob + * @deprecated use {@link #getType(int, int)} instead + */ + public boolean isBlob(int row, int col) { //deprecated + cursorWindowHelper.acquireReference(); + try { + return isBlob_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Checks if a field contains a long + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return {@code true} if given field is a long + * @deprecated use {@link #getType(int, int)} instead + */ + public boolean isLong(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return isInteger_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Checks if a field contains a float. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return {@code true} if given field is a float + * @deprecated use {@link #getType(int, int)} instead + */ + public boolean isFloat(int row, int col) { //deprecated + cursorWindowHelper.acquireReference(); + try { + return isFloat_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Checks if a field contains either a String or is null. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return {@code true} if given field is {@code NULL} or a String + * @deprecated use {@link #getType(int, int)} instead + */ + public boolean isString(int row, int col) { //deprecated + cursorWindowHelper.acquireReference(); + try { + return isString_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native boolean isBlob_native(int row, int col); + private native boolean isString_native(int row, int col); + private native boolean isInteger_native(int row, int col); + private native boolean isFloat_native(int row, int col); + + private native int getType_native(int row, int col); + + /** + * Returns a String for the given field. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a String value for the given field + */ + public String getString(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return getString_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns the value at (row, col) as a String. + * + *

If the value is null, then null is returned. If the + * type of column col is integral, then the result is the string + * that is obtained by formatting the integer value with the printf + * family of functions using format specifier %lld. If the + * type of column col is floating-point, then the result is the string + * that is obtained by formatting the floating-point value with the + * printf family of functions using format specifier %g. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + * + * @param row row + * @param col column + * @return string + */ + private native String getString_native(int row, int col); + //private native byte[] getString_native(int row, int col); + + /** + * copy the text for the given field in the provided char array. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @param buffer the CharArrayBuffer to copy the text into, + * If the requested string is larger than the buffer + * a new char buffer will be created to hold the string. and assigne to + * CharArrayBuffer.data + */ + public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("CharArrayBuffer should not be null"); + } + if (buffer.data == null) { + buffer.data = new char[64]; + } + cursorWindowHelper.acquireReference(); + try { + char[] newbuf = copyStringToBuffer_native( + row - mStartPos, col, buffer.data.length, buffer); //todo hos native api is required + if (newbuf != null) { + buffer.data = newbuf; + } + } finally { + cursorWindowHelper.releaseReference(); + } + } + + private native char[] copyStringToBuffer_native( + int row, int col, int bufferSize, CharArrayBuffer buffer); + + /** + * Returns a long for the given field. + * row is 0 based + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a long value for the given field + */ + public long getLong(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return getLong_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns the value at (row, col) as a long. + * + *

If the value is null, then 0L is returned. If the + * type of column col is a string type, then the result + * is the long that is obtained by parsing the string value with + * strtoll. If the type of column col is + * floating-point, then the result is the floating-point value casted to a long. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + * + * @param row row + * @param col column + * @return value + */ + private native long getLong_native(int row, int col); + + /** + * Returns a double for the given field. + * row is 0 based + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a double value for the given field + */ + public double getDouble(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return getDouble_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns the value at (row, col) as a double. + * + *

If the value is null, then 0.0 is returned. If the + * type of column col is a string type, then the result + * is the double that is obtained by parsing the string value with + * strtod. If the type of column col is + * integral, then the result is the integer value casted to a double. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + * + * @param row row + * @param col column + * @return value + */ + private native double getDouble_native(int row, int col); + + /** + * Returns a short for the given field. + * row is 0 based + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a short value for the given field + */ + public short getShort(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return (short) getLong_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns an int for the given field. + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return an int value for the given field + */ + public int getInt(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return (int) getLong_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Returns a float for the given field. + * row is 0 based + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return a float value for the given field + */ + public float getFloat(int row, int col) { + cursorWindowHelper.acquireReference(); + try { + return (float) getDouble_native(row - mStartPos, col); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** + * Clears out the existing contents of the window, making it safe to reuse + * for new data. Note that the number of columns in the window may NOT + * change across a call to clear(). + */ + public void clear() { + cursorWindowHelper.acquireReference(); + try { + mStartPos = 0; + native_clear(); + } finally { + cursorWindowHelper.releaseReference(); + } + } + + /** Clears out the native side of things */ + private native void native_clear(); + + /** + * Cleans up the native resources associated with the window. + */ + public void close() { + cursorWindowHelper.releaseReference(); + } + + private native void close_native(); + + @Override + protected void finalize() { + // Just in case someone forgot to call close... + if (nWindow == 0) { + return; + } + close_native(); + } + + public static final Producer CREATOR + = new Producer() { + public CursorWindow createFromParcel(Parcel source) { + return new CursorWindow(source,0); + } + public CursorWindow[] newArray(int size) { + return new CursorWindow[size]; + } + }; + + public boolean unmarshalling(Parcel p) { + return false; //not used + } + + public static CursorWindow createFromParcel(Parcel parcel) { + return CREATOR.createFromParcel(parcel); + } + + public int describeContents() { + return 0; + } + + public boolean marshalling(Parcel dest, int flags) { + ((MessageParcel) dest).writeRemoteObject(native_getBinder()); + return dest.writeInt(mStartPos); + } + + public CursorWindow(Parcel source,int foo) { + super(""); + IRemoteObject nativeBinder = ((MessageParcel) source).readRemoteObject(); + mStartPos = source.readInt(); + + native_init(nativeBinder); + } + + public void acquireRef() { + cursorWindowHelper.acquireReference(); + } + + public void releaseRef() { + cursorWindowHelper.releaseReference(); + } + + /** Get the binder for the native side of the window + * @return IRemoteObject + */ + private native IRemoteObject native_getBinder(); + + /** Does the native side initialization for an empty window + * @param localOnly flag + * @param initialSize initialSize + * @param growthPaddingSize growthPaddingSize + * @param maxSize max size + */ + private native void native_init(boolean localOnly, long initialSize, + long growthPaddingSize, long maxSize); + + /** Does the native side initialization with an existing binder from another process + * @param nativeBinder IRemoteObject + */ + private native void native_init(IRemoteObject nativeBinder); + + protected void onAllReferencesReleased() { + close_native(); + // super.onAllReferencesReleased(); //todo no such method in superclass + } +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java b/sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java new file mode 100644 index 0000000000000000000000000000000000000000..6b4c47f25f42f3c319e29bf8bc8442becf394d74 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java @@ -0,0 +1,7 @@ +package net.sqlcipher; + +public interface CursorWindowAllocation { + long getInitialAllocationSize(); + long getGrowthPaddingSize(); + long getMaxAllocationSize(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/CursorWindowHelper.java b/sqlcipher/src/main/java/net/sqlcipher/CursorWindowHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..496c22913eba4101a2c82a6459084c1745d10c15 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CursorWindowHelper.java @@ -0,0 +1,11 @@ +package net.sqlcipher; + +import net.sqlcipher.database.SQLiteClosableHos; + +public class CursorWindowHelper extends SQLiteClosableHos { + + @Override + protected void onAllReferencesReleased() { + //no implementation can be provided now , native dependency on dispose(); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java b/sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..fa2b49275a78dfcc527da83ad958c74ed8944b76 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import ohos.data.resultset.ResultSet; + +/** + * Extension of ohos.data.resultset.ResultSetWrapper to support getColumnTypeForIndex(). + */ +public class CursorWrapper extends ohos.data.resultset.ResultSetWrapper implements ResultSet { + + private final ResultSet mCursor; + + public CursorWrapper(ResultSet cursor) { + super(cursor); + mCursor = cursor; + } + + public ColumnType getColumnTypeForIndex(int columnIndex) { + return mCursor.getColumnTypeForIndex(columnIndex); + } + + public ResultSet getResultSet() { + return mCursor; + } +} + + diff --git a/sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java b/sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java new file mode 100644 index 0000000000000000000000000000000000000000..9575dafe768300430eea4550f7655bd07645ed15 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java @@ -0,0 +1,30 @@ +package net.sqlcipher; + +import net.sqlcipher.CursorWindowAllocation; + +public class CustomCursorWindowAllocation implements CursorWindowAllocation { + + private long initialAllocationSize = 0L; + private long growthPaddingSize = 0L; + private long maxAllocationSize = 0L; + + public CustomCursorWindowAllocation(long initialSize, + long growthPaddingSize, + long maxAllocationSize){ + this.initialAllocationSize = initialSize; + this.growthPaddingSize = growthPaddingSize; + this.maxAllocationSize = maxAllocationSize; + } + + public long getInitialAllocationSize() { + return initialAllocationSize; + } + + public long getGrowthPaddingSize() { + return growthPaddingSize; + } + + public long getMaxAllocationSize() { + return maxAllocationSize; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java b/sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..58096f1d8947f1cf08b03adc3e093636eb04bcdc --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import net.sqlcipher.database.SQLiteDatabase; + +/** + * An interface to let the apps define the actions to take when the following errors are detected + * database corruption + */ +public interface DatabaseErrorHandler { + + /** + * defines the method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + void onCorruption(SQLiteDatabase dbObj); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java b/sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..407b1f125b135d10c6582cbb6c23efe3fd967c5a --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java @@ -0,0 +1,1267 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import net.sqlcipher.database.SQLiteAbortException; +import net.sqlcipher.database.SQLiteConstraintException; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteDatabaseCorruptException; +import net.sqlcipher.database.SQLiteDiskIOException; +import net.sqlcipher.database.SQLiteException; +import net.sqlcipher.database.SQLiteFullException; +import net.sqlcipher.database.SQLiteProgram; +import net.sqlcipher.database.SQLiteStatement; + +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.text.Collator; +import java.util.HashMap; +import java.util.Map; + +import ohos.data.rdb.ValuesBucket; + +import ohos.rpc.MessageParcel; +import ohos.utils.Parcel; +import net.sqlcipher.utils.TextUtils; + +import ohos.data.resultset.ResultSet; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * Static utility methods for dealing with databases and {@link Cursor}s. + */ +public class DatabaseUtils { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x002C, "DatabaseUtils"); + + private static final boolean DEBUG = false; + // private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; + + private static final String[] countProjection = new String[]{"count(*)"}; + + /** + * Special function for writing an exception result at the header of + * a parcel, to be used when returning an exception from a transaction. + * exception will be re-thrown by the function in another process + * @param reply Parcel to write to + * @param e The Exception to be written. + * @see Parcel#writeNoException + * @see Parcel#writeException + */ + public static final void writeExceptionToParcel(Parcel reply, Exception e) { + int code = 0; + boolean logException = true; + if (e instanceof FileNotFoundException) { + code = 1; + logException = false; + } else if (e instanceof IllegalArgumentException) { + code = 2; + } else if (e instanceof UnsupportedOperationException) { + code = 3; + } else if (e instanceof SQLiteAbortException) { + code = 4; + } else if (e instanceof SQLiteConstraintException) { + code = 5; + } else if (e instanceof SQLiteDatabaseCorruptException) { + code = 6; + } else if (e instanceof SQLiteFullException) { + code = 7; + } else if (e instanceof SQLiteDiskIOException) { + code = 8; + } else if (e instanceof SQLiteException) { + code = 9; + } /*else if (e instanceof OperationApplicationException) { + code = 10; + } */else { + + ((MessageParcel)reply).writeException(e); + HiLog.error(label, "Writing exception to parcel", e); + return; + } + reply.writeInt(code); + reply.writeString(e.getMessage()); + + if (logException) { + HiLog.error(label, "Writing exception to parcel", e); + } + } + + /** + * Special function for reading an exception result from the header of + * a parcel, to be used after receiving the result of a transaction. This + * will throw the exception for you if it had been written to the Parcel, + * otherwise return and let you read the normal result data from the Parcel. + * @param reply Parcel to read from + * @see Parcel#writeNoException + * @see Parcel#readException + */ + public static final void readExceptionFromParcel(Parcel reply) { + int code = reply.readInt(); + if (code == 0) return; + String msg = reply.readString(); + DatabaseUtils.readExceptionFromParcel(reply, msg, code); + } + + public static void readExceptionWithFileNotFoundExceptionFromParcel( + Parcel reply) throws FileNotFoundException { + int code = reply.readInt(); + if (code == 0) return; + String msg = reply.readString(); + if (code == 1) { + throw new FileNotFoundException(msg); + } else { + DatabaseUtils.readExceptionFromParcel(reply, msg, code); + } + } + +/* public static void readExceptionWithOperationApplicationExceptionFromParcel( + Parcel reply) throws OperationApplicationException { + int code = reply.readInt(); + if (code == 0) return; + String msg = reply.readString(); + if (code == 10) { + throw new OperationApplicationException(msg); + } else { + DatabaseUtils.readExceptionFromParcel(reply, msg, code); + } + }*/ + + private static final void readExceptionFromParcel(Parcel reply, String msg, int code) { + switch (code) { + case 2: + throw new IllegalArgumentException(msg); + case 3: + throw new UnsupportedOperationException(msg); + case 4: + throw new SQLiteAbortException(msg); + case 5: + throw new SQLiteConstraintException(msg); + case 6: + throw new SQLiteDatabaseCorruptException(msg); + case 7: + throw new SQLiteFullException(msg); + case 8: + throw new SQLiteDiskIOException(msg); + case 9: + throw new SQLiteException(msg); + default: + ((MessageParcel)reply).readException(); + } + } + + /** + * Binds the given Object to the given SQLiteProgram using the proper + * typing. For example, bind numbers as longs/doubles, and everything else + * as a string by call toString() on it. + * + * @param prog the program to bind the object to + * @param index the 1-based index to bind at + * @param value the value to bind + */ + public static void bindObjectToProgram(SQLiteProgram prog, int index, + Object value) { + if (value == null) { + prog.bindNull(index); + } else if (value instanceof Double || value instanceof Float) { + prog.bindDouble(index, ((Number)value).doubleValue()); + } else if (value instanceof Number) { + prog.bindLong(index, ((Number)value).longValue()); + } else if (value instanceof Boolean) { + Boolean bool = (Boolean)value; + if (bool) { + prog.bindLong(index, 1); + } else { + prog.bindLong(index, 0); + } + } else if (value instanceof byte[]){ + prog.bindBlob(index, (byte[]) value); + } else { + prog.bindString(index, value.toString()); + } + } + + /** + * Returns data type of the given object's value. + *

+ * Returned values are + *

    + *
  • {@link Cursor#FIELD_TYPE_NULL}
  • + *
  • {@link Cursor#FIELD_TYPE_INTEGER}
  • + *
  • {@link Cursor#FIELD_TYPE_FLOAT}
  • + *
  • {@link Cursor#FIELD_TYPE_STRING}
  • + *
  • {@link Cursor#FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param obj the object whose value type is to be returned + * @return object value type + * @hide + */ + public static ResultSet.ColumnType getTypeOfObject(Object obj) { + if (obj == null) { + return ResultSet.ColumnType.TYPE_NULL; /* Cursor.FIELD_TYPE_NULL */ + } else if (obj instanceof byte[]) { + return ResultSet.ColumnType.TYPE_BLOB; /* Cursor.FIELD_TYPE_BLOB */ + } else if (obj instanceof Float || obj instanceof Double) { + return ResultSet.ColumnType.TYPE_FLOAT; /* Cursor.FIELD_TYPE_FLOAT */ + } else if (obj instanceof Long || obj instanceof Integer) { + return ResultSet.ColumnType.TYPE_INTEGER; /* Cursor.FIELD_TYPE_INTEGER */ + } else { + return ResultSet.ColumnType.TYPE_STRING; /* Cursor.FIELD_TYPE_STRING */ + } + } + + /** + * Appends an SQL string to the given StringBuilder, including the opening + * and closing single quotes. Any single quotes internal to sqlString will + * be escaped. + * + * This method is deprecated because we want to encourage everyone + * to use the "?" binding form. However, when implementing a + * ContentProvider, one may want to add WHERE clauses that were + * not provided by the caller. Since "?" is a positional form, + * using it in this case could break the caller because the + * indexes would be shifted to accomodate the ContentProvider's + * internal bindings. In that case, it may be necessary to + * construct a WHERE clause manually. This method is useful for + * those cases. + * + * @param sb the StringBuilder that the SQL string will be appended to + * @param sqlString the raw string to be appended, which may contain single + * quotes + */ + public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { + sb.append('\''); + if (sqlString.indexOf('\'') != -1) { + int length = sqlString.length(); + for (int i = 0; i < length; i++) { + char c = sqlString.charAt(i); + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } + } else + sb.append(sqlString); + sb.append('\''); + } + + /** + * SQL-escape a string. + * @param value value + * @return string + */ + public static String sqlEscapeString(String value) { + StringBuilder escaper = new StringBuilder(); + + DatabaseUtils.appendEscapedSQLString(escaper, value); + + return escaper.toString(); + } + + /** + * Appends an Object to an SQL string with the proper escaping, etc. + * @param sql string builder + * @param value value + */ + public static final void appendValueToSql(StringBuilder sql, Object value) { + if (value == null) { + sql.append("NULL"); + } else if (value instanceof Boolean) { + Boolean bool = (Boolean)value; + if (bool) { + sql.append('1'); + } else { + sql.append('0'); + } + } else { + appendEscapedSQLString(sql, value.toString()); + } + } + + /** + * Concatenates two SQL WHERE clauses, handling empty or null values. + * @hide + * @param a string + * @param b string + * @return string + */ + public static String concatenateWhere(String a, String b) { + if (TextUtils.isEmpty(a)) { + return b; + } + if (TextUtils.isEmpty(b)) { + return a; + } + + return "(" + a + ") AND (" + b + ")"; + } + + /** + * return the collation key + * @param name + * @return the collation key + */ + public static String getCollationKey(String name) { + byte [] arr = getCollationKeyInBytes(name); + try { + return new String(arr, 0, getKeyLen(arr), "ISO8859_1"); + } catch (Exception ex) { + return ""; + } + } + + /** + * return the collation key in hex format + * @param name + * @return the collation key in hex format + */ + public static String getHexCollationKey(String name) { + byte [] arr = getCollationKeyInBytes(name); + char[] keys = encodeHex(arr, HEX_DIGITS_LOWER); + return new String(keys, 0, getKeyLen(arr) * 2); + } + + private static final char[] HEX_DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + private static int getKeyLen(byte[] arr) { + if (arr[arr.length - 1] != 0) { + return arr.length; + } else { + // remove zero "termination" + return arr.length-1; + } + } + + private static byte[] getCollationKeyInBytes(String name) { + if (mColl == null) { + mColl = Collator.getInstance(); + mColl.setStrength(Collator.PRIMARY); + } + return mColl.getCollationKey(name).toByteArray(); + } + + private static Collator mColl = null; + /** + * Prints the contents of a Cursor to System.out. The position is restored + * after printing. + * + * @param cursor the cursor to print + */ + public static void dumpCursor(ResultSet cursor) { + dumpCursor(cursor, System.out); + } + + /** + * Prints the contents of a Cursor to a PrintSteam. The position is restored + * after printing. + * + * @param cursor the cursor to print + * @param stream the stream to print to + */ + public static void dumpCursor(ResultSet cursor, PrintStream stream) { + stream.println(">>>>> Dumping cursor " + cursor); + if (cursor != null) { + int startPos = cursor.getRowIndex(); + + cursor.goToRow(-1); + while (cursor.goToNextRow()) { + dumpCurrentRow(cursor, stream); + } + cursor.goToRow(startPos); + } + stream.println("<<<<<"); + } + + /** + * Prints the contents of a Cursor to a StringBuilder. The position + * is restored after printing. + * + * @param cursor the cursor to print + * @param sb the StringBuilder to print to + */ + public static void dumpCursor(ResultSet cursor, StringBuilder sb) { + sb.append(">>>>> Dumping cursor " + cursor + "\n"); + if (cursor != null) { + int startPos = cursor.getRowIndex(); + + cursor.goToRow(-1); + while (cursor.goToNextRow()) { + dumpCurrentRow(cursor, sb); + } + cursor.goToRow(startPos); + } + sb.append("<<<<<\n"); + } + + /** + * Prints the contents of a Cursor to a String. The position is restored + * after printing. + * + * @param cursor the cursor to print + * @return a String that contains the dumped cursor + */ + public static String dumpCursorToString(ResultSet cursor) { + StringBuilder sb = new StringBuilder(); + dumpCursor(cursor, sb); + return sb.toString(); + } + + /** + * Prints the contents of a Cursor's current row to System.out. + * + * @param cursor the cursor to print from + */ + public static void dumpCurrentRow(ResultSet cursor) { + dumpCurrentRow(cursor, System.out); + } + + /** + * Prints the contents of a Cursor's current row to a PrintSteam. + * + * @param cursor the cursor to print + * @param stream the stream to print to + */ + public static void dumpCurrentRow(ResultSet cursor, PrintStream stream) { + String[] cols = cursor.getAllColumnNames(); + stream.println("" + cursor.getRowIndex() + " {"); + int length = cols.length; + for (int i = 0; i< length; i++) { + String value; + try { + value = cursor.getString(i); + } catch (SQLiteException e) { + // assume that if the getString threw this exception then the column is not + // representable by a string, e.g. it is a BLOB. + value = ""; + } + stream.println(" " + cols[i] + '=' + value); + } + stream.println("}"); + } + + /** + * Prints the contents of a Cursor's current row to a StringBuilder. + * + * @param cursor the cursor to print + * @param sb the StringBuilder to print to + */ + public static void dumpCurrentRow(ResultSet cursor, StringBuilder sb) { + String[] cols = cursor.getAllColumnNames(); + sb.append("" + cursor.getRowIndex() + " {\n"); + int length = cols.length; + for (int i = 0; i < length; i++) { + String value; + try { + value = cursor.getString(i); + } catch (SQLiteException e) { + // assume that if the getString threw this exception then the column is not + // representable by a string, e.g. it is a BLOB. + value = ""; + } + sb.append(" " + cols[i] + '=' + value + "\n"); + } + sb.append("}\n"); + } + + /** + * Dump the contents of a Cursor's current row to a String. + * + * @param cursor the cursor to print + * @return a String that contains the dumped cursor row + */ + public static String dumpCurrentRowToString(ResultSet cursor) { + StringBuilder sb = new StringBuilder(); + dumpCurrentRow(cursor, sb); + return sb.toString(); + } + + /** + * Reads a String out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The TEXT field to read + * @param values The ContentValues to put the value into, with the field as the key + */ + public static void cursorStringToContentValues(ResultSet cursor, String field, + ValuesBucket values) { + cursorStringToContentValues(cursor, field, values, field); + } + + /** + * Reads a String out of a field in a Cursor and writes it to an InsertHelper. + * + * @param cursor The cursor to read from + * @param field The TEXT field to read + * @param inserter The InsertHelper to bind into + * @param index the index of the bind entry in the InsertHelper + */ + public static void cursorStringToInsertHelper(ResultSet cursor, String field, + InsertHelper inserter, int index) { + inserter.bind(index, cursor.getString(getColumnIndexOrThrow(cursor, field))); + } + + /** + * Reads a String out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The TEXT field to read + * @param values The ContentValues to put the value into, with the field as the key + * @param key The key to store the value with in the map + */ + public static void cursorStringToContentValues(ResultSet cursor, String field, + ValuesBucket values, String key) { + values.putString(key, cursor.getString(getColumnIndexOrThrow(cursor, field))); + } + + /** + * Reads an Integer out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The INTEGER field to read + * @param values The ContentValues to put the value into, with the field as the key + */ + public static void cursorIntToContentValues(ResultSet cursor, String field, ValuesBucket values) { + cursorIntToContentValues(cursor, field, values, field); + } + + /** + * Reads a Integer out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The INTEGER field to read + * @param values The ContentValues to put the value into, with the field as the key + * @param key The key to store the value with in the map + */ + public static void cursorIntToContentValues(ResultSet cursor, String field, ValuesBucket values, + String key) { + int colIndex = cursor.getColumnIndexForName(field); + if (!cursor.isColumnNull(colIndex)) { + values.putInteger(key, cursor.getInt(colIndex)); + } else { + values.putInteger(key, (Integer) null); + } + } + + /** + * Reads a Long out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The INTEGER field to read + * @param values The ContentValues to put the value into, with the field as the key + */ + public static void cursorLongToContentValues(ResultSet cursor, String field, ValuesBucket values) + { + cursorLongToContentValues(cursor, field, values, field); + } + + /** + * Reads a Long out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The INTEGER field to read + * @param values The ContentValues to put the value into + * @param key The key to store the value with in the map + */ + public static void cursorLongToContentValues(ResultSet cursor, String field, ValuesBucket values, + String key) { + int colIndex = cursor.getColumnIndexForName(field); + if (!cursor.isColumnNull(colIndex)) { + Long value = Long.valueOf(cursor.getLong(colIndex)); + values.putLong(key, value); + } else { + values.putLong(key, (Long) null); + } + } + + /** + * Reads a Double out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The REAL field to read + * @param values The ContentValues to put the value into + */ + public static void cursorDoubleToCursorValues(ResultSet cursor, String field, ValuesBucket values) + { + cursorDoubleToContentValues(cursor, field, values, field); + } + + /** + * Reads a Double out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The REAL field to read + * @param values The ContentValues to put the value into + * @param key The key to store the value with in the map + */ + public static void cursorDoubleToContentValues(ResultSet cursor, String field, + ValuesBucket values, String key) { + int colIndex = cursor.getColumnIndexForName(field); + if (!cursor.isColumnNull(colIndex)) { + values.putDouble(key, cursor.getDouble(colIndex)); + } else { + values.putDouble(key, (Double) null); + } + } + + /** + * Read the entire contents of a cursor row and store them in a ContentValues. + * + * @param cursor the cursor to read from. + * @param values the ContentValues to put the row into. + */ + public static void cursorRowToContentValues(ResultSet cursor, ValuesBucket values) { + AbstractWindowedCursor awc = + (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null; + + String[] columns = cursor.getAllColumnNames(); + int length = columns.length; + for (int i = 0; i < length; i++) { + if (awc != null && awc.isBlob(i)) { + values.putByteArray(columns[i], cursor.getBlob(i)); + } else { + values.putString(columns[i], cursor.getString(i)); + } + } + } + + /** + * Query the table for the number of rows in the table. + * @param db the database the table is in + * @param table the name of the table to query + * @return the number of rows in the table + */ + public static long queryNumEntries(SQLiteDatabase db, String table) { + ResultSet cursor = db.query(table, countProjection, + null, null, null, null, null); + try { + cursor.goToFirstRow(); + return cursor.getLong(0); + } finally { + cursor.close(); + } + } + + /** + * Utility method to run the query on the db and return the value in the + * first column of the first row. + * @param db database + * @param query query + * @param selectionArgs arguments + * @return value + */ + public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { + SQLiteStatement prog = db.compileStatement(query); + try { + return longForQuery(prog, selectionArgs); + } finally { + prog.close(); + } + } + + /** + * Utility method to run the pre-compiled query and return the value in the + * first column of the first row. + * @param prog SQLiteStatement + * @param selectionArgs arguments + * @return value + */ + public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) { + if (selectionArgs != null) { + int size = selectionArgs.length; + for (int i = 0; i < size; i++) { + bindObjectToProgram(prog, i + 1, selectionArgs[i]); + } + } + long value = prog.simpleQueryForLong(); + return value; + } + + /** + * Utility method to run the query on the db and return the value in the + * first column of the first row. + * @param db database + * @param query query + * @param selectionArgs arguments + * @return string + */ + public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { + SQLiteStatement prog = db.compileStatement(query); + try { + return stringForQuery(prog, selectionArgs); + } finally { + prog.close(); + } + } + + /** + * Utility method to run the pre-compiled query and return the value in the + * first column of the first row. + * @param prog SQLiteStatement + * @param selectionArgs arguments + * @return string + */ + public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) { + if (selectionArgs != null) { + int size = selectionArgs.length; + for (int i = 0; i < size; i++) { + bindObjectToProgram(prog, i + 1, selectionArgs[i]); + } + } + String value = prog.simpleQueryForString(); + return value; + } + + /** + * Reads a String out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorStringToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putString(column, cursor.getString(index)); + } + } + + /** + * Reads a Long out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorLongToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putLong(column, cursor.getLong(index)); + } + } + + /** + * Reads a Short out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorShortToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putShort(column, cursor.getShort(index)); + } + } + + /** + * Reads a Integer out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorIntToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putInteger(column, cursor.getInt(index)); + } + } + + /** + * Reads a Float out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorFloatToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putFloat(column, cursor.getFloat(index)); + } + } + + /** + * Reads a Double out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The ContentValues to put the value into + */ + public static void cursorDoubleToContentValuesIfPresent(ResultSet cursor, ValuesBucket values, + String column) { + final int index = getColumnIndexOrThrow(cursor, column); + if (!cursor.isColumnNull(index)) { + values.putDouble(column, cursor.getDouble(index)); + } + } + + /** + * This class allows users to do multiple inserts into a table but + * compile the SQL insert statement only once, which may increase + * performance. + */ + public static class InsertHelper { + private final SQLiteDatabase mDb; + private final String mTableName; + private HashMap mColumns; + private String mInsertSQL = null; + private SQLiteStatement mInsertStatement = null; + private SQLiteStatement mReplaceStatement = null; + private SQLiteStatement mPreparedStatement = null; + + /** + * {@hide} + * + * These are the columns returned by sqlite's "PRAGMA + * table_info(...)" command that we depend on. + */ + public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1; + public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4; + + /** + * @param db the SQLiteDatabase to insert into + * @param tableName the name of the table to insert into + */ + public InsertHelper(SQLiteDatabase db, String tableName) { + mDb = db; + mTableName = tableName; + } + + private void buildSQL() throws SQLException { + StringBuilder sb = new StringBuilder(128); + sb.append("INSERT INTO "); + sb.append(mTableName); + sb.append(" ("); + + StringBuilder sbv = new StringBuilder(128); + sbv.append("VALUES ("); + + int i = 1; + ResultSet cur = null; + try { + cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null); + mColumns = new HashMap(cur.getRowCount()); + while (cur.goToNextRow()) { + String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX); + String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX); + + mColumns.put(columnName, i); + sb.append("'"); + sb.append(columnName); + sb.append("'"); + + if (defaultValue == null) { + sbv.append("?"); + } else { + sbv.append("COALESCE(?, "); + sbv.append(defaultValue); + sbv.append(")"); + } + + sb.append(i == cur.getRowCount() ? ") " : ", "); + sbv.append(i == cur.getRowCount() ? ");" : ", "); + ++i; + } + } finally { + if (cur != null) cur.close(); + } + + sb.append(sbv); + + mInsertSQL = sb.toString(); + /* if (LOCAL_LOGV)*/ HiLog.debug(label, "insert statement is " + mInsertSQL); + } + + private SQLiteStatement getStatement(boolean allowReplace) throws SQLException { + if (allowReplace) { + if (mReplaceStatement == null) { + if (mInsertSQL == null) buildSQL(); + // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead. + String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6); + mReplaceStatement = mDb.compileStatement(replaceSQL); + } + return mReplaceStatement; + } else { + if (mInsertStatement == null) { + if (mInsertSQL == null) buildSQL(); + mInsertStatement = mDb.compileStatement(mInsertSQL); + } + return mInsertStatement; + } + } + + /** + * Performs an insert, adding a new row with the given values. + * + * @param values the set of values with which to populate the + * new row + * @param allowReplace if true, the statement does "INSERT OR + * REPLACE" instead of "INSERT", silently deleting any + * previously existing rows that would cause a conflict + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + private synchronized long insertInternal(ValuesBucket values, boolean allowReplace) { + try { + SQLiteStatement stmt = getStatement(allowReplace); + stmt.clearBindings(); + /*if (LOCAL_LOGV)*/ HiLog.debug(label, "--- inserting in table " + mTableName); + for (Map.Entry e: values.getAll()) { + final String key = e.getKey(); + int i = getColumnIndex(key); + DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue()); + /* if (LOCAL_LOGV) { */ + HiLog.debug(label, "binding " + e.getValue() + " to column " + + i + " (" + key + ")"); + //} + } + return stmt.executeInsert(); + } catch (SQLException e) { + HiLog.error(label, "Error inserting " + values + " into table " + mTableName, e); + return -1; + } + } + + /** + * Returns the index of the specified column. This is index is suitagble for use + * in calls to bind(). + * @param key the column name + * @return the index of the column + */ + public int getColumnIndex(String key) { + getStatement(false); + final Integer index = mColumns.get(key); + if (index == null) { + throw new IllegalArgumentException("column '" + key + "' is invalid"); + } + return index; + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, double value) { + mPreparedStatement.bindDouble(index, value); + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, float value) { + mPreparedStatement.bindDouble(index, value); + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, long value) { + mPreparedStatement.bindLong(index, value); + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, int value) { + mPreparedStatement.bindLong(index, value); + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, boolean value) { + mPreparedStatement.bindLong(index, value ? 1 : 0); + } + + /** + * Bind null to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + */ + public void bindNull(int index) { + mPreparedStatement.bindNull(index); + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, byte[] value) { + if (value == null) { + mPreparedStatement.bindNull(index); + } else { + mPreparedStatement.bindBlob(index, value); + } + } + + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, String value) { + if (value == null) { + mPreparedStatement.bindNull(index); + } else { + mPreparedStatement.bindString(index, value); + } + } + + /** + * Performs an insert, adding a new row with the given values. + * If the table contains conflicting rows, an error is + * returned. + * + * @param values the set of values with which to populate the + * new row + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long insert(ValuesBucket values) { + return insertInternal(values, false); + } + + /** + * Execute the previously prepared insert or replace using the bound values + * since the last call to prepareForInsert or prepareForReplace. + * + *

Note that calling bind() and then execute() is not thread-safe. The only thread-safe + * way to use this class is to call insert() or replace(). + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long execute() { + if (mPreparedStatement == null) { + throw new IllegalStateException("you must prepare this inserter before calling " + + "execute"); + } + try { + /*if (LOCAL_LOGV)*/ HiLog.debug(label, "--- doing insert or replace in table " + mTableName); + return mPreparedStatement.executeInsert(); + } catch (SQLException e) { + HiLog.error(label, "Error executing InsertHelper with table " + mTableName, e); + return -1; + } finally { + // you can only call this once per prepare + mPreparedStatement = null; + } + } + + /** + * Prepare the InsertHelper for an insert. The pattern for this is: + *

    + *
  • prepareForInsert() + *
  • bind(index, value); + *
  • bind(index, value); + *
  • ... + *
  • bind(index, value); + *
  • execute(); + *
+ */ + public void prepareForInsert() { + mPreparedStatement = getStatement(false); + mPreparedStatement.clearBindings(); + } + + /** + * Prepare the InsertHelper for a replace. The pattern for this is: + *
    + *
  • prepareForReplace() + *
  • bind(index, value); + *
  • bind(index, value); + *
  • ... + *
  • bind(index, value); + *
  • execute(); + *
+ */ + public void prepareForReplace() { + mPreparedStatement = getStatement(true); + mPreparedStatement.clearBindings(); + } + + /** + * Performs an insert, adding a new row with the given values. + * If the table contains conflicting rows, they are deleted + * and replaced with the new row. + * + * @param values the set of values with which to populate the + * new row + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long replace(ValuesBucket values) { + return insertInternal(values, true); + } + + /** + * Close this object and release any resources associated with + * it. The behavior of calling insert() after + * calling this method is undefined. + */ + public void close() { + if (mInsertStatement != null) { + mInsertStatement.close(); + mInsertStatement = null; + } + if (mReplaceStatement != null) { + mReplaceStatement.close(); + mReplaceStatement = null; + } + mInsertSQL = null; + mColumns = null; + } + } + + public static void cursorFillWindow(final ResultSet cursor, + int position, final ohos.data.resultset.SharedBlock window) { + if (position < 0 || position >= cursor.getRowCount()) { + return; + } + final int oldPos = cursor.getRowIndex(); + final int numColumns = cursor.getColumnCount(); + window.clear(); + window.setStartRowIndex(position); + window.setColumnCount(numColumns); + if (cursor.goToRow(position)) { + do { + if (!window.allocateRow()) { + break; + } + for (int i = 0; i < numColumns; i++) { + final ResultSet.ColumnType type = cursor.getColumnTypeForIndex(i); + final boolean success; + switch (type) { + case TYPE_NULL: + success = window.putNull(position, i); + break; + + case TYPE_INTEGER: + success = window.putLong(cursor.getLong(i), position, i); + break; + + case TYPE_FLOAT: + success = window.putDouble(cursor.getDouble(i), position, i); + break; + + case TYPE_BLOB: { + final byte[] value = cursor.getBlob(i); + success = value != null ? window.putBlob(value, position, i) + : window.putNull(position, i); + break; + } + + default: // assume value is convertible to String + case TYPE_STRING: { + final String value = cursor.getString(i); + success = value != null ? window.putString(value, position, i) + : window.putNull(position, i); + break; + } + } + if (!success) { + window.freeLastRow(); + break; + } + } + position += 1; + } while (cursor.goToNextRow()); + } + cursor.goToRow(oldPos); + } + + public static int getColumnIndexOrThrow(ResultSet cursor, String columnName) { + final int index = cursor.getColumnIndexForName(columnName); + if (index < 0) { + throw new IllegalArgumentException("column '" + columnName + "' does not exist"); + } + return index; + } + + /** + * Creates a db and populates it with the sql statements in sqlStatements. + * + * @param context the context to use to create the db + * @param dbName the name of the db to create + * @param dbVersion the version to set on the db + * @param sqlStatements the statements to use to populate the db. This should be a single string + * of the form returned by sqlite3's .dump command (statements separated by + * semicolons) + */ + /* + static public void createDbFromSqlStatements( + Context context, String dbName, int dbVersion, String sqlStatements) { + + //TODO TODO TODO what needs ot happen here + SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null); + + // TODO: this is not quite safe since it assumes that all semicolons at the end of a line + // terminate statements. It is possible that a text field contains ;\n. We will have to fix + // this if that turns out to be a problem. + String[] statements = TextUtils.split(sqlStatements, ";\n"); + for (String statement : statements) { + if (TextUtils.isEmpty(statement)) continue; + db.execSQL(statement); + } + db.setVersion(dbVersion); + db.close(); + }*/ +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java b/sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java new file mode 100644 index 0000000000000000000000000000000000000000..47b5f5480e103395b39cd7e92fbfe66baaba4407 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java @@ -0,0 +1,21 @@ +package net.sqlcipher; + +import net.sqlcipher.CursorWindowAllocation; + +public class DefaultCursorWindowAllocation implements CursorWindowAllocation { + + private long initialAllocationSize = 1024 * 1024; + private long WindowAllocationUnbounded = 0; + + public long getInitialAllocationSize() { + return initialAllocationSize; + } + + public long getGrowthPaddingSize() { + return initialAllocationSize; + } + + public long getMaxAllocationSize() { + return WindowAllocationUnbounded; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java b/sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..2de2e98f5085ba97503d2fa43ca36b841ca31ffc --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import java.io.File; +import java.util.List; + +import net.sqlcipher.database.SQLiteDatabase; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * Default class used to define the actions to take when the database corruption is reported + * by sqlite. + *

+ * If null is specified for DatabaeErrorHandler param in the above calls, then this class is used + * as the default {@link DatabaseErrorHandler}. + */ +public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler { + + private final String TAG = getClass().getSimpleName(); + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00209, "DefaultDatabaseErrorHandler"); + + /** + * defines the default method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + + public void onCorruption(SQLiteDatabase dbObj) { + // NOTE: Unlike the AOSP, this version does NOT attempt to delete any attached databases. + // TBD: Are we really certain that the attached databases would really be corrupt? + HiLog.error(label, "Corruption reported by sqlite on database, deleting: " + dbObj.getPath()); + + if (dbObj.isOpen()) { + HiLog.error(label, "Database object for corrupted database is already open, closing"); + + try { + dbObj.close(); + } catch (Exception e) { + /* ignored */ + HiLog.error(label, "Exception closing Database object for corrupted database, ignored", e); + } + } + + deleteDatabaseFile(dbObj.getPath()); + } + + private void deleteDatabaseFile(String fileName) { + if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) { + return; + } + HiLog.error(label, "deleting the database file: " + fileName); + try { + new File(fileName).delete(); + } catch (Exception e) { + /* print warning and ignore exception */ + HiLog.warn(label, "delete failed: " + e.getMessage()); + } + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java b/sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..f5f399d7526e33a5b378c2290f521d86d2736831 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import ohos.rpc.RemoteException; +import ohos.rpc.IRemoteBroker; +import ohos.rpc.IRemoteObject; +import ohos.utils.PacMap; + +import java.util.Map; + +/** + * This interface provides a low-level way to pass bulk cursor data across + * both process and language boundries. Application code should use the Cursor + * interface directly. + * + * {@hide} + */ +public interface IBulkCursor extends IRemoteBroker { + /** + * Returns a BulkCursorWindow, which either has a reference to a shared + * memory segment with the rows, or an array of JSON strings. + * @param startPos startPos + * @return CursorWindow + * @throws RemoteException RemoteException + */ + public CursorWindow getWindow(int startPos) throws RemoteException; + + public void onMove(int position) throws RemoteException; + + /** + * Returns the number of rows in the cursor. + * + * @return the number of rows in the cursor. + * @throws RemoteException RemoteException + */ + public int count() throws RemoteException; + + /** + * Returns a string array holding the names of all of the columns in the + * cursor in the order in which they were listed in the result. + * + * @return the names of the columns returned in this query. + * @throws RemoteException RemoteException + */ + public String[] getColumnNames() throws RemoteException; + + public boolean updateRows(Map> values) throws RemoteException; + + public boolean deleteRow(int position) throws RemoteException; + + public void deactivate() throws RemoteException; + + public void close() throws RemoteException; + + // public int requery(IContentObserver observer, CursorWindow window) throws RemoteException; + + boolean getWantsAllOnMoveCalls() throws RemoteException; + + PacMap getExtras() throws RemoteException; + + PacMap respond(PacMap extras) throws RemoteException; + + /* IPC constants */ + static final String descriptor = "IBulkCursor"; + + static final int GET_CURSOR_WINDOW_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID; + static final int COUNT_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 1; + static final int GET_COLUMN_NAMES_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 2; + static final int UPDATE_ROWS_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 3; + static final int DELETE_ROW_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 4; + static final int DEACTIVATE_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 5; + static final int REQUERY_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 6; + static final int ON_MOVE_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 7; + static final int WANTS_ON_MOVE_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 8; + static final int GET_EXTRAS_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 9; + static final int RESPOND_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 10; + static final int CLOSE_TRANSACTION = IRemoteObject.MIN_TRANSACTION_ID + 11; +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java b/sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java new file mode 100644 index 0000000000000000000000000000000000000000..275b28d9fc1ff2fbbbec2e28b450d7eb04a07303 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java @@ -0,0 +1,14 @@ +package net.sqlcipher; + +/** + * An exception that indicates there was an error accessing a specific row/column. + */ +public class InvalidRowColumnException extends RuntimeException +{ + public InvalidRowColumnException() {} + + public InvalidRowColumnException(String error) + { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java b/sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..996bd4c9eb69a59a2f0e9dcd557c8dc015bef043 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import java.util.ArrayList; +import ohos.data.resultset.ResultSet; + +/** + * A mutable cursor implementation backed by an array of {@code Object}s. Use + * {@link #newRow()} to add rows. Automatically expands internal capacity + * as needed. + */ +public class MatrixCursor extends AbstractCursor { + + private final String[] columnNames; + private Object[] data; + private int rowCount = 0; + private final int columnCount; + + /** + * Constructs a new cursor with the given initial capacity. + * + * @param columnNames names of the columns, the ordering of which + * determines column ordering elsewhere in this cursor + * @param initialCapacity in rows + */ + public MatrixCursor(String[] columnNames, int initialCapacity) { + this.columnNames = columnNames; + this.columnCount = columnNames.length; + + if (initialCapacity < 1) { + initialCapacity = 1; + } + + this.data = new Object[columnCount * initialCapacity]; + } + + /** + * Constructs a new cursor. + * + * @param columnNames names of the columns, the ordering of which + * determines column ordering elsewhere in this cursor + */ + public MatrixCursor(String[] columnNames) { + this(columnNames, 16); + } + + /** + * Gets value at the given column for the current row. + * @param column column + * @return value + */ + private Object get(int column) { + if (column < 0 || column >= columnCount) { + throw new CursorIndexOutOfBoundsException("Requested column: " + + column + ", # of columns: " + columnCount); + } + if (mPos < 0) { + throw new CursorIndexOutOfBoundsException("Before first row."); + } + if (mPos >= rowCount) { + throw new CursorIndexOutOfBoundsException("After last row."); + } + return data[mPos * columnCount + column]; + } + + /** + * Adds a new row to the end and returns a builder for that row. Not safe + * for concurrent use. + * + * @return builder which can be used to set the column values for the new + * row + */ + public RowBuilder addRowByBuilder() { + rowCount++; + int endIndex = rowCount * columnCount; + ensureCapacity(endIndex); + int start = endIndex - columnCount; + return new RowBuilder(start, endIndex); + } + + /** + * Adds a new row to the end with the given column values. Not safe + * for concurrent use. + * + * @throws IllegalArgumentException if {@code columnValues.length != + * columnNames.length} + * @param columnValues in the same order as the the column names specified + * at cursor construction time + */ + public void addRow(Object[] columnValues) { + if (columnValues.length != columnCount) { + throw new IllegalArgumentException("columnNames.length = " + + columnCount + ", columnValues.length = " + + columnValues.length); + } + + int start = rowCount++ * columnCount; + ensureCapacity(start + columnCount); + System.arraycopy(columnValues, 0, data, start, columnCount); + } + + /** + * Adds a new row to the end with the given column values. Not safe + * for concurrent use. + * + * @throws IllegalArgumentException if {@code columnValues.size() != + * columnNames.length} + * @param columnValues in the same order as the the column names specified + * at cursor construction time + */ + public void addRow(Iterable columnValues) { + int start = rowCount * columnCount; + int end = start + columnCount; + ensureCapacity(end); + + if (columnValues instanceof ArrayList) { + addRow((ArrayList) columnValues, start); + return; + } + + int current = start; + Object[] localData = data; + for (Object columnValue : columnValues) { + if (current == end) { + // TODO: null out row? + throw new IllegalArgumentException( + "columnValues.size() > columnNames.length"); + } + localData[current++] = columnValue; + } + + if (current != end) { + // TODO: null out row? + throw new IllegalArgumentException( + "columnValues.size() < columnNames.length"); + } + + // Increase row count here in case we encounter an exception. + rowCount++; + } + + /** Optimization for {@link ArrayList}. + * @param columnValues columnValues + * @param start start + */ + private void addRow(ArrayList columnValues, int start) { + int size = columnValues.size(); + if (size != columnCount) { + throw new IllegalArgumentException("columnNames.length = " + + columnCount + ", columnValues.size() = " + size); + } + + rowCount++; + Object[] localData = data; + for (int i = 0; i < size; i++) { + localData[start + i] = columnValues.get(i); + } + } + + /** Ensures that this cursor has enough capacity. + * @param size size + */ + private void ensureCapacity(int size) { + if (size > data.length) { + Object[] oldData = this.data; + int newSize = data.length * 2; + if (newSize < size) { + newSize = size; + } + this.data = new Object[newSize]; + System.arraycopy(oldData, 0, this.data, 0, oldData.length); + } + } + + /** + * Builds a row, starting from the left-most column and adding one column + * value at a time. Follows the same ordering as the column names specified + * at cursor construction time. + */ + public class RowBuilder { + + private int index; + private final int endIndex; + + RowBuilder(int index, int endIndex) { + this.index = index; + this.endIndex = endIndex; + } + + /** + * Sets the next column value in this row. + * + * @param columnValue columnValue + * @throws CursorIndexOutOfBoundsException if you try to add too many + * values + * @return this builder to support chaining + */ + public RowBuilder add(Object columnValue) { + if (index == endIndex) { + throw new CursorIndexOutOfBoundsException( + "No more columns left."); + } + + data[index++] = columnValue; + return this; + } + } + + // AbstractCursor implementation. + + @Override + public int getRowCount() { + return rowCount; + } + + @Override + public String[] getAllColumnNames() { + return columnNames; + } + + @Override + public String getString(int column) { + Object value = get(column); + if (value == null) return null; + return value.toString(); + } + + @Override + public short getShort(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).shortValue(); + return Short.parseShort(value.toString()); + } + + @Override + public int getInt(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).intValue(); + return Integer.parseInt(value.toString()); + } + + @Override + public long getLong(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).longValue(); + return Long.parseLong(value.toString()); + } + + @Override + public float getFloat(int column) { + Object value = get(column); + if (value == null) return 0.0f; + if (value instanceof Number) return ((Number) value).floatValue(); + return Float.parseFloat(value.toString()); + } + + @Override + public double getDouble(int column) { + Object value = get(column); + if (value == null) return 0.0d; + if (value instanceof Number) return ((Number) value).doubleValue(); + return Double.parseDouble(value.toString()); + } + + @Override + public ColumnType getColumnTypeForIndex(int column) { + return DatabaseUtils.getTypeOfObject(get(column)); + } + + @Override + public boolean isColumnNull(int column) { + return get(column) == null; + } + +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java b/sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java new file mode 100644 index 0000000000000000000000000000000000000000..a468056818c17887de8f842400ae78e27851fa8f --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java @@ -0,0 +1,15 @@ +package net.sqlcipher; + +/** + * An exception that indicates there was an error attempting to allocate a row + * for the CursorWindow. + */ +public class RowAllocationException extends RuntimeException +{ + public RowAllocationException() {} + + public RowAllocationException(String error) + { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/SQLException.java b/sqlcipher/src/main/java/net/sqlcipher/SQLException.java new file mode 100644 index 0000000000000000000000000000000000000000..28481288bec3cde919fd2d40c58f6a0b16db828d --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/SQLException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +/** + * An exception that indicates there was an error with SQL parsing or execution. + */ +public class SQLException extends RuntimeException { + public SQLException() {} + + public SQLException(String error) + { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java b/sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java new file mode 100644 index 0000000000000000000000000000000000000000..02692828d4a186b7b37ea23bf9642bb4bac3bda1 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +/** + * This exception is thrown when a Cursor contains stale data and must be + * requeried before being used again. + */ +public class StaleDataException extends RuntimeException +{ + public StaleDataException() + { + super(); + } + + public StaleDataException(String description) + { + super(description); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java b/sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java new file mode 100644 index 0000000000000000000000000000000000000000..4da359ff9e7c16ff2294c38369134b6a44ed0a7a --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java @@ -0,0 +1,14 @@ +package net.sqlcipher; + +/** + * An exception that indicates an unknown type was returned. + */ +public class UnknownTypeException extends RuntimeException +{ + public UnknownTypeException() {} + + public UnknownTypeException(String error) + { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java b/sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java new file mode 100644 index 0000000000000000000000000000000000000000..89f2adf7c5bd98f91febcd6d74b937b557e924c3 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that garbage-collector is finalizing a database object + * that is not explicitly closed + * @hide + */ +public class DatabaseObjectNotClosedException extends RuntimeException +{ + private static final String s = "Application did not close the cursor or database object " + + "that was opened here"; + + public DatabaseObjectNotClosedException() + { + super(s); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteAbortException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteAbortException.java new file mode 100644 index 0000000000000000000000000000000000000000..89b066ceca697684d6b70f743f9064074ead4eed --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteAbortException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that the SQLite program was aborted. + * This can happen either through a call to ABORT in a trigger, + * or as the result of using the ABORT conflict clause. + */ +public class SQLiteAbortException extends SQLiteException { + public SQLiteAbortException() {} + + public SQLiteAbortException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java new file mode 100644 index 0000000000000000000000000000000000000000..6e506ccc1f3b015adf95a25a22761a893349c459 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.*; + +/** + * An object created from a SQLiteDatabase that can be closed. + */ +public abstract class SQLiteClosable { + private int mReferenceCount = 1; + private Object mLock = new Object(); + + protected abstract void onAllReferencesReleased(); + protected void onAllReferencesReleasedFromContainer() {} + + public void acquireReference() { + synchronized(mLock) { + if (mReferenceCount <= 0) { + throw new IllegalStateException( + "attempt to re-open an already-closed object: " + getObjInfo()); + } + mReferenceCount++; + } + } + + public void releaseReference() { + synchronized(mLock) { + mReferenceCount--; + if (mReferenceCount == 0) { + onAllReferencesReleased(); + } + } + } + + public void releaseReferenceFromContainer() { + synchronized(mLock) { + mReferenceCount--; + if (mReferenceCount == 0) { + onAllReferencesReleasedFromContainer(); + } + } + } + + private String getObjInfo() { + StringBuilder buff = new StringBuilder(); + buff.append(this.getClass().getName()); + buff.append(" ("); + if (this instanceof SQLiteDatabase) { + buff.append("database = "); + buff.append(((SQLiteDatabase)this).getPath()); + } else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement || + this instanceof SQLiteQuery) { + buff.append("mSql = "); + buff.append(((SQLiteProgram)this).mSql); + } + /*else if (this instanceof CursorWindow) { + buff.append("mStartPos = "); + buff.append(((CursorWindow)this).getStartPosition()); + }*/ + buff.append(") "); + return buff.toString(); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosableHos.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosableHos.java new file mode 100644 index 0000000000000000000000000000000000000000..0ed49ce7ebc7fd9b8d0d0ad11692f25733af9455 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosableHos.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.io.Closeable; + +/** + * An object created from a SQLiteDatabase that can be closed. + * + * This class implements a primitive reference counting scheme for database objects. + */ +public abstract class SQLiteClosableHos implements Closeable { + private int mReferenceCount = 1; + + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReference()} or {@link #close()}. + */ + protected abstract void onAllReferencesReleased(); + + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReferenceFromContainer()}. + * + * @deprecated Do not use. + */ + @Deprecated + protected void onAllReferencesReleasedFromContainer() { + onAllReferencesReleased(); + } + + /** + * Acquires a reference to the object. + * + * @throws IllegalStateException if the last reference to the object has already + * been released. + */ + public void acquireReference() { + synchronized(this) { + if (mReferenceCount <= 0) { + throw new IllegalStateException( + "attempt to re-open an already-closed object: " + this); + } + mReferenceCount++; + } + } + + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * @see #onAllReferencesReleased() + */ + public void releaseReference() { + boolean refCountIsZero = false; + synchronized(this) { + refCountIsZero = --mReferenceCount == 0; + } + if (refCountIsZero) { + onAllReferencesReleased(); + } + } + + /** + * Releases a reference to the object that was owned by the container of the object, + * closing the object if the last reference was released. + * + * @see #onAllReferencesReleasedFromContainer() + * @deprecated Do not use. + */ + @Deprecated + public void releaseReferenceFromContainer() { + boolean refCountIsZero = false; + synchronized(this) { + refCountIsZero = --mReferenceCount == 0; + } + if (refCountIsZero) { + onAllReferencesReleasedFromContainer(); + } + } + + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * Calling this method is equivalent to calling {@link #releaseReference}. + * + * @see #releaseReference() + * @see #onAllReferencesReleased() + */ + public void close() { + releaseReference(); + } +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java new file mode 100644 index 0000000000000000000000000000000000000000..8443ca3621e01288f71100b98847d2f78ed815cd --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * This class encapsulates compilation of sql statement and release of the compiled statement obj. + * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase} + * and it is released in one of the 2 following ways + * 1. when {@link SQLiteDatabase} object is closed. + * 2. if this is not cached in {@link SQLiteDatabase} + * releaases this obj. + */ +/* package */ class SQLiteCompiledSql { + + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00205, "SQLiteCompiledSql"); + + /** The database this program is compiled against. */ + /* package */ SQLiteDatabase mDatabase; + + /** + * Native linkage, do not modify. This comes from the database. + */ + /* package */ long nHandle = 0; + + /** + * Native linkage, do not modify. When non-0 this holds a reference to a valid + * sqlite3_statement object. It is only updated by the native code, but may be + * checked in this class when the database lock is held to determine if there + * is a valid native-side program or not. + */ + /* package */ long nStatement = 0; + + /** the following are for debugging purposes */ + private String mSqlStmt = null; + + /** when in cache and is in use, this member is set */ + private boolean mInUse = false; + + /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) { + if (!db.isOpen()) { + throw new IllegalStateException("database " + db.getPath() + " already closed"); + } + mDatabase = db; + mSqlStmt = sql; + this.nHandle = db.mNativeHandle; + compile(sql, true); + } + + /** + * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If + * this method has been called previously without a call to close and forCompilation is set + * to false the previous compilation will be used. Setting forceCompilation to true will + * always re-compile the program and should be done if you pass differing SQL strings to this + * method. + * + *

Note: this method acquires the database lock.

+ * + * @param sql the SQL string to compile + * @param forceCompilation forces the SQL to be recompiled in the event that there is an + * existing compiled SQL program already around + */ + private void compile(String sql, boolean forceCompilation) { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + // Only compile if we don't have a valid statement already or the caller has + // explicitly requested a recompile. + if (forceCompilation) { + mDatabase.lock(); + try { + // Note that the native_compile() takes care of destroying any previously + // existing programs before it compiles. + native_compile(sql); + } finally { + mDatabase.unlock(); + } + } + } + + /* package */ void releaseSqlStatement() { + // Note that native_finalize() checks to make sure that nStatement is + // non-null before destroying it. + if (nStatement != 0) { + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "closed and deallocated DbObj (id#" + nStatement +")"); + } + try { + mDatabase.lock(); + native_finalize(); + nStatement = 0; + } finally { + mDatabase.unlock(); + } + } + } + + /** + * returns true if acquire() succeeds. false otherwise. + * @return true/false + */ + /* package */ synchronized boolean acquire() { + if (mInUse) { + // someone already has acquired it. + return false; + } + mInUse = true; + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "Acquired DbObj (id#" + nStatement + ") from DB cache"); + } + return true; + } + + /* package */ synchronized void release() { + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "Released DbObj (id#" + nStatement + ") back to DB cache"); + } + mInUse = false; + } + + /** + * Make sure that the native resource is cleaned up. + * @throws Throwable Throwable + */ + @Override + protected void finalize() throws Throwable { + try { + if (nStatement == 0) return; + // finalizer should NEVER get called + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "** warning ** Finalized DbObj (id#" + nStatement + ")"); + } + releaseSqlStatement(); + } finally { + super.finalize(); + } + } + + /** + * Compiles SQL into a SQLite program. + * + *

The database lock must be held when calling this method. + * @param sql The SQL to compile. + */ + private final native void native_compile(String sql); + private final native void native_finalize(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteConstraintException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteConstraintException.java new file mode 100644 index 0000000000000000000000000000000000000000..d9d548f6760e7936eaa4d71fdba7069f08b633c2 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteConstraintException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that an integrity constraint was violated. + */ +public class SQLiteConstraintException extends SQLiteException { + public SQLiteConstraintException() {} + + public SQLiteConstraintException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7c9a56c5a2b1a159940d7c397dbf88c29485255d --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import ohos.app.Context; +import ohos.global.resource.RawFileDescriptor; +import ohos.data.resultset.ResultSet; +import ohos.global.resource.RawFileEntry; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.rpc.MessageParcel; +import ohos.rpc.ReliableFileDescriptor; +import ohos.security.asset.AssetOperator; + +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Some helper functions for using SQLite database to implement content providers. + * + * @hide + */ +public class SQLiteContentHelper { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x002A, "SQLiteContentHelper"); + private static ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + + /** + * Runs an SQLite query and returns an AssetFileDescriptor for the + * blob in column 0 of the first row. If the first column does + * not contain a blob, an unspecified exception is thrown. + * + * @param db Handle to a readable database. + * @param sql SQL query, possibly with query arguments. + * @param selectionArgs Query argument values, or {@code null} for no argument. + * @return If no exception is thrown, a non-null AssetFileDescriptor is returned. + * @throws FileNotFoundException If the query returns no results or the + * value of column 0 is NULL, or if there is an error creating the + * asset file descriptor. + */ +/* public static RawFileDescriptor getBlobColumnAsAssetFile(Context context, SQLiteDatabase db, String sql, + String[] selectionArgs) throws FileNotFoundException { + FileDescriptor fd = null; + + try { + final MessageParcel file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs); + if (file == null) { + throw new FileNotFoundException("No results."); + } + fd = file.readFileDescriptor(); + RawFileEntry assetManager = context.getResourceManager().getRawFileEntry(file); + RawFileDescriptor afd = assetManager.openRawFileDescriptor(); + return afd; + } catch (IOException ex) { + throw new FileNotFoundException(ex.toString()); + } + }*/ + + /** + * Runs an SQLite query and returns a MemoryFile for the + * blob in column 0 of the first row. If the first column does + * not contain a blob, an unspecified exception is thrown. + * + * @param db database + * @param sql string + * @param selectionArgs arguments + * @return A memory file, or {@code null} if the query returns no results + * or the value column 0 is NULL. + * @throws IOException If there is an error creating the memory file. + */ + // TODO: make this native and use the SQLite blob API to reduce copying + private static MessageParcel simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql, + String[] selectionArgs) throws IOException { + ResultSet cursor = db.rawQuery(sql, selectionArgs); + if (cursor == null) { + return null; + } + try { + if (!cursor.goToFirstRow()) { + return null; + } + byte[] bytes = cursor.getBlob(0); + if (bytes == null) { + return null; + } + buffer.put(bytes, 0, bytes.length); + buffer.flip(); + MessageParcel file = MessageParcel.obtain(); + file.writeRawData(bytes, bytes.length); + + // file.deactivate(); + return file; + } finally { + cursor.close(); + } + } + +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java new file mode 100644 index 0000000000000000000000000000000000000000..f9eeb86f201f214e811fee60c029b75e3b2900c3 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.AbstractWindowedCursor; +import net.sqlcipher.CursorWindow; +import net.sqlcipher.SQLException; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import ohos.data.rdb.DataObserver; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.eventhandler.InnerEvent; +import ohos.os.ProcessManager; +import net.sqlcipher.utils.TextUtils; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * A Cursor implementation that exposes results from a query on a + * {@link SQLiteDatabase}. + * + * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple + * threads should perform its own synchronization when using the SQLiteCursor. + */ +public class SQLiteCursor extends AbstractWindowedCursor { + static final String TAG = "Cursor"; + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x002A, "SQLiteCursor"); + static final int NO_COUNT = -1; + + /** The name of the table to edit */ + private String mEditTable; + + /** The names of the columns in the rows */ + private String[] mColumns; + + /** The query object for the cursor */ + private SQLiteQuery mQuery; + + /** The database the cursor was created from */ + private SQLiteDatabase mDatabase; + + /** The compiled query this cursor came from */ + private SQLiteCursorDriver mDriver; + + /** The number of rows in the cursor */ + private int mCount = NO_COUNT; + + private int mCursorWindowCapacity = 0; + + private boolean fillWindowForwardOnly = false; + + /** A mapping of column names to column indices, to speed up lookups */ + private Map mColumnNameMap; + + /** Used to find out where a cursor was allocated in case it never got released. */ + private Throwable mStackTrace; + + /** + * mMaxRead is the max items that each cursor window reads + * default to a very high value + */ + private int mMaxRead = Integer.MAX_VALUE; + private int mInitialRead = Integer.MAX_VALUE; + private int mCursorState = 0; + private ReentrantLock mLock = null; + private boolean mPendingData = false; + + public void setFillWindowForwardOnly(boolean value) { + fillWindowForwardOnly = value; + } + + /** + * support for a cursor variant that doesn't always read all results + * initialRead is the initial number of items that cursor window reads + * if query contains more than this number of items, a thread will be + * created and handle the left over items so that caller can show + * results as soon as possible + * @param initialRead initial number of items that cursor read + * @param maxRead leftover items read at maxRead items per time + * @hide + */ + public void setLoadStyle(int initialRead, int maxRead) { + mMaxRead = maxRead; + mInitialRead = initialRead; + mLock = new ReentrantLock(true); + } + + private void queryThreadLock() { + if (mLock != null) { + mLock.lock(); + } + } + + private void queryThreadUnlock() { + if (mLock != null) { + mLock.unlock(); + } + } + + + /** + * @hide + */ + final private class QueryThread implements Runnable { + private final int mThreadState; + QueryThread(int version) { + mThreadState = version; + } + private void sendMessage() { + if (mNotificationHandler != null) { + mNotificationHandler.sendEvent(1); + mPendingData = false; + } else { + mPendingData = true; + } + + } + public void run() { + // use cached mWindow, to avoid get null mWindow + CursorWindow cw = mWindow; + ProcessManager.setThreadPriority(ProcessManager.getTid(), 10); + // the cursor's state doesn't change + while (true) { + if(mLock == null){ + mLock = new ReentrantLock(true); + } + mLock.lock(); + if (mCursorState != mThreadState) { + mLock.unlock(); + break; + } + try { + int count = mQuery.fillWindow(cw, mMaxRead, mCount); + // return -1 means not finished + if (count != 0) { + if (count == NO_COUNT){ + mCount += mMaxRead; + sendMessage(); + } else { + mCount = count; + sendMessage(); + break; + } + } else { + break; + } + } catch (Exception e) { + // end the tread when the cursor is close + break; + } finally { + mLock.unlock(); + } + } + } + } + + + /** + * @hide + */ + protected static class MainThreadNotificationHandler extends EventHandler { + private final WeakReference wrappedCursor; + + MainThreadNotificationHandler(SQLiteCursor cursor,EventRunner runner) { + super(runner); + wrappedCursor = new WeakReference(cursor); + } + + public void processEvent(InnerEvent event) { + SQLiteCursor cursor = wrappedCursor.get(); + if (cursor != null) { + cursor.notifyDataSetChange(); + } + } + } + + /** + * @hide + */ + protected MainThreadNotificationHandler mNotificationHandler; + + public void registerObserver(DataObserver observer) { //todo + super.registerObserver(observer); + if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && + mNotificationHandler == null) { + queryThreadLock(); + try { + EventRunner runner = EventRunner.create(true); + mNotificationHandler = new MainThreadNotificationHandler(this, runner); + if (mPendingData) { + notifyDataSetChange(); + mPendingData = false; + } + } finally { + queryThreadUnlock(); + } + } + } + + /** + * Execute a query and provide access to its result set through a Cursor + * interface. For a query such as: {@code SELECT name, birth, phone FROM + * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, + * phone) would be in the projection argument and everything from + * {@code FROM} onward would be in the params argument. This constructor + * has package scope. + * + * @param db a reference to a Database object that is already constructed + * and opened + * @param editTable the name of the table used for this query + * @param query the rest of the query terms + * cursor is finalized + */ + public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, + String editTable, SQLiteQuery query) { + // The AbstractCursor constructor needs to do some setup. + super(); + mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + mDatabase = db; + mDriver = driver; + mEditTable = editTable; + mColumnNameMap = null; + mQuery = query; + + try { + db.lock(); + + // Setup the list of columns + int columnCount = mQuery.columnCountLocked(); + mColumns = new String[columnCount]; + + // Read in all column names + for (int i = 0; i < columnCount; i++) { + String columnName = mQuery.columnNameLocked(i); + mColumns[i] = columnName; + HiLog.debug(label,"DatabaseWindow", "mColumns[" + i + "] is " + + mColumns[i]); + + // Make note of the row ID column index for quick access to it + if ("_id".equals(columnName)) { + mRowIdColumnIndex = i; + } + } + } finally { + db.unlock(); + } + } + + /** + * getDatabase + * @return the SQLiteDatabase that this cursor is associated with. + */ + public SQLiteDatabase getDatabase() { + return mDatabase; + } + + @Override + public boolean onGo(int oldPosition, int newPosition) { + // Make sure the row at newPosition is present in the window + if (mWindow == null || newPosition < mWindow.getStartRowIndex() || + newPosition >= (mWindow.getStartRowIndex() + mWindow.getRowCount())) { + fillWindow(newPosition); + } + return true; + } + + @Override + public int getRowCount() { + if (mCount == NO_COUNT) { + fillWindow(0); + } + return mCount; + } + + private void fillWindow (int requiredPos) { + int startPos = 0; + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } else { + mCursorState++; + queryThreadLock(); + try { + mWindow.clear(); + } finally { + queryThreadUnlock(); + } + } + if(fillWindowForwardOnly) { + startPos = requiredPos; + } else { + startPos = mCount == NO_COUNT + ? cursorPickFillWindowStartPosition(requiredPos, 0) + : cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity); + } + mWindow.setStartRowIndex(startPos); + mWindow.setRequiredPosition(requiredPos); + HiLog.debug(label, String.format("Filling cursor window with start position:%d required position:%d", + startPos, requiredPos)); + mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); + if(mCursorWindowCapacity == 0) { + mCursorWindowCapacity = mWindow.getRowCount(); + } + // return -1 means not finished + if (mCount == NO_COUNT){ + mCount = startPos + mInitialRead; + Thread t = new Thread(new QueryThread(mCursorState), "query thread"); + t.start(); + } + } + + @Override + public int getColumnIndexForName(String columnName) { + // Create mColumnNameMap on demand + if (mColumnNameMap == null) { + String[] columns = mColumns; + int columnCount = columns.length; + HashMap map = new HashMap(columnCount, 1); + for (int i = 0; i < columnCount; i++) { + map.put(columns[i], i); + } + mColumnNameMap = map; + } + + // Hack according to bug 903852 + final int periodIndex = columnName.lastIndexOf('.'); + if (periodIndex != -1) { + Exception e = new Exception(); + HiLog.error(label, "requesting column name with table name -- " + columnName, e); + columnName = columnName.substring(periodIndex + 1); + } + + Integer i = mColumnNameMap.get(columnName); + if (i != null) { + return i.intValue(); + } else { + return -1; + } + } + + /** + * deleteRow + * @hide + * @deprecated + * @return true/false + */ + public boolean deleteRow() { + checkPosition(); + + // Only allow deletes if there is an ID column, and the ID has been read from it + if (mRowIdColumnIndex == -1 || mCurrentRowID == null) { + HiLog.error(label, "Could not delete row because either the row ID column is not available or it" + + "has not been read."); + return false; + } + boolean success; + + /* + * Ensure we don't change the state of the database when another + * thread is holding the database lock. requery() and moveTo() are also + * synchronized here to make sure they get the state of the database + * immediately following the DELETE. + */ + mDatabase.lock(); + try { + try { + mDatabase.delete(mEditTable, mColumns[mRowIdColumnIndex] + "=?", + new String[] {mCurrentRowID.toString()}); + success = true; + } catch (SQLException e) { + success = false; + } + + int pos = mPos; + requery(); + + /* + * Ensure proper cursor state. Note that mCurrentRowID changes + * in this call. + */ + goToRow(pos); + } finally { + mDatabase.unlock(); + } + if (success) { + onChange(true); + return true; + } else { + return false; + } + } + + @Override + public String[] getAllColumnNames() { + return mColumns; + } + + /** + * supportsUpdates + * @hide + * @deprecated + * @return true/false + */ + public boolean supportsUpdates() { + // return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable); + return !TextUtils.isEmpty(mEditTable); + } + + /** + * commitUpdates + * @hide + * @deprecated + * @param additionalValues additionalValues + * @return true/false + */ + // @Override + public boolean commitUpdates(Map> additionalValues) { + if (!supportsUpdates()) { + HiLog.error(label, "commitUpdates not supported on this cursor, did you " + + "include the _id column?"); + return false; + } + + /* + * Prevent other threads from changing the updated rows while they're + * being processed here. + */ + synchronized (mUpdatedRows) { + if (additionalValues != null) { + mUpdatedRows.putAll(additionalValues); + } + + if (mUpdatedRows.size() == 0) { + return true; + } + + /* + * Prevent other threads from changing the database state while + * we process the updated rows, and prevents us from changing the + * database behind the back of another thread. + */ + mDatabase.beginTransaction(); + try { + StringBuilder sql = new StringBuilder(128); + + // For each row that has been updated + for (Map.Entry> rowEntry : + mUpdatedRows.entrySet()) { + Map values = rowEntry.getValue(); + Long rowIdObj = rowEntry.getKey(); + + if (rowIdObj == null || values == null) { + throw new IllegalStateException("null rowId or values found! rowId = " + + rowIdObj + ", values = " + values); + } + + if (values.size() == 0) { + continue; + } + + long rowId = rowIdObj.longValue(); + + Iterator> valuesIter = + values.entrySet().iterator(); + + sql.setLength(0); + sql.append("UPDATE " + mEditTable + " SET "); + + // For each column value that has been updated + Object[] bindings = new Object[values.size()]; + int i = 0; + while (valuesIter.hasNext()) { + Map.Entry entry = valuesIter.next(); + sql.append(entry.getKey()); + sql.append("=?"); + bindings[i] = entry.getValue(); + if (valuesIter.hasNext()) { + sql.append(", "); + } + i++; + } + + sql.append(" WHERE " + mColumns[mRowIdColumnIndex] + + '=' + rowId); + sql.append(';'); + mDatabase.execSQL(sql.toString(), bindings); + mDatabase.rowUpdated(mEditTable, rowId); + } + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + + mUpdatedRows.clear(); + } + + // Let any change observers know about the update + onChange(true); + + return true; + } + + private void deactivateCommon() { + HiLog.debug(label, "<<< Releasing cursor " + this); + mCursorState = 0; + if (mWindow != null) { + mWindow.close(); + mWindow = null; + } + HiLog.debug(label,"DatabaseWindow", "closing window in release()"); + } + + @Override + public void deactivate() { + super.deactivate(); + deactivateCommon(); + mDriver.cursorDeactivated(); + } + + @Override + public void close() { + super.close(); + deactivateCommon(); + mQuery.close(); + mDriver.cursorClosed(); + } + + @Override + public boolean requery() { + if (isClosed()) { + return false; + } + long timeStart = 0; + timeStart = System.currentTimeMillis(); + + /* + * Synchronize on the database lock to ensure that mCount matches the + * results of mQuery.requery(). + */ + mDatabase.lock(); + try { + if (mWindow != null) { + mWindow.clear(); + } + mPos = -1; + // This one will recreate the temp table, and get its count + mDriver.cursorRequeried(this); + mCount = NO_COUNT; + mCursorState++; + queryThreadLock(); + try { + mQuery.requery(); + } finally { + queryThreadUnlock(); + } + } finally { + mDatabase.unlock(); + } + + HiLog.debug(label,"DatabaseWindow", "closing window in requery()"); + HiLog.debug(label, "--- Requery()ed cursor " + this + ": " + mQuery); + + boolean result = super.requery(); + long timeEnd = System.currentTimeMillis(); + HiLog.debug(label, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString()); + return result; + } + + @Override + public void setWindow(CursorWindow window) { + if (mWindow != null) { + mCursorState++; + queryThreadLock(); + try { + mWindow.close(); + } finally { + queryThreadUnlock(); + } + mCount = NO_COUNT; + } + mWindow = window; + } + + /** + * Changes the selection arguments. The new values take effect after a call to requery(). + * @param selectionArgs arguments + */ + public void setSelectionArguments(String[] selectionArgs) { + mDriver.setBindArguments(selectionArgs); + } + + /** + * Release the native resources, if they haven't been released yet. + */ + @Override + protected void finalize() { + try { + // if the cursor hasn't been closed yet, close it first + if (mWindow != null) { + int len = mQuery.mSql.length(); + HiLog.error(label, "Finalizing a Cursor that has not been deactivated or closed. " + + "database = " + mDatabase.getPath() + ", table = " + mEditTable + + ", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len), + mStackTrace); + close(); + SQLiteDebug.notifyActiveCursorFinalized(); + } else { + HiLog.error(label, "Finalizing cursor on database = " + mDatabase.getPath() + + ", table = " + mEditTable + ", query = " + mQuery.mSql); + } + } finally { + super.finalize(); + } + } + + + + @Override + public void fillBlock(int requiredPos, ohos.data.resultset.SharedBlock window) { + int startPos = 0; + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } else { + mCursorState++; + queryThreadLock(); + try { + mWindow.clear(); + } finally { + queryThreadUnlock(); + } + } + if(fillWindowForwardOnly) { + startPos = requiredPos; + } else { + startPos = mCount == NO_COUNT + ? cursorPickFillWindowStartPosition(requiredPos, 0) + : cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity); + } + mWindow.setStartRowIndex(startPos); + mWindow.setRequiredPosition(requiredPos); + HiLog.error(label, String.format("Filling cursor window with start position:%d required position:%d", + startPos, requiredPos)); + mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); + if(mCursorWindowCapacity == 0) { + mCursorWindowCapacity = mWindow.getRowCount(); + } + // return -1 means not finished + if (mCount == NO_COUNT){ + mCount = startPos + mInitialRead; + Thread t = new Thread(new QueryThread(mCursorState), "query thread"); + t.start(); + } + } + + public int cursorPickFillWindowStartPosition( + int cursorPosition, int cursorWindowCapacity) { + return Math.max(cursorPosition - cursorWindowCapacity / 3, 0); + } + +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java new file mode 100644 index 0000000000000000000000000000000000000000..d261a6ce4e59e8de6cea46d26358f29a555fb972 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import ohos.data.resultset.ResultSet; + +/** + * A driver for SQLiteCursors that is used to create them and gets notified + * by the cursors it creates on significant events in their lifetimes. + */ +public interface SQLiteCursorDriver { + /** + * Executes the query returning a Cursor over the result set. + * + * @param factory The CursorFactory to use when creating the Cursors, or + * null if standard SQLiteCursors should be returned. + * @param bindArgs arguments + * @return a Cursor over the result set + */ + ResultSet query(SQLiteDatabase.CursorFactory factory, String[] bindArgs); // for HOS + + /** + * Called by a SQLiteCursor when it is released. + */ + void cursorDeactivated(); + + /** + * Called by a SQLiteCursor when it is requeryed. + * + * @param cursor ResultSet + * @return The new count value. + */ + void cursorRequeried(ResultSet cursor); + + /** + * Called by a SQLiteCursor when it it closed to destroy this object as well. + */ + void cursorClosed(); + + /** + * Set new bind arguments. These will take effect in cursorRequeried(). + * @param bindArgs the new arguments + */ + public void setBindArguments(String[] bindArgs); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..b77011c805f3a991d9fa787bfb5227239597c352 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java @@ -0,0 +1,3311 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.CrossProcessCursorWrapper; +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.DatabaseUtils; +import net.sqlcipher.DefaultDatabaseErrorHandler; +import net.sqlcipher.SQLException; +import net.sqlcipher.database.SQLiteDebug.DbStats; +import net.sqlcipher.database.SQLiteQueryStats; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; +import java.util.zip.ZipInputStream; + +import ohos.app.Context; +import ohos.data.rdb.ValuesBucket; +import ohos.data.resultset.ResultSet; +import ohos.hiviewdfx.Debug; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import net.sqlcipher.utils.TextUtils; +import ohos.utils.Pair; +import ohos.miscservices.timeutility.Time; + +import net.sqlcipher.harmonyx.SupportSQLiteDatabase; +import net.sqlcipher.harmonyx.SupportSQLiteQuery; + +/** + * Exposes methods to manage a SQLCipher database. + *

SQLiteDatabase has methods to create, delete, execute SQL commands, and + * perform other common database management tasks. + *

A call to loadLibs(…) should occur before attempting to + * create or open a database connection. + *

Database names must be unique within an application, not across all + * applications. + * + */ +public class SQLiteDatabase extends SQLiteClosable implements + SupportSQLiteDatabase { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00204, "SQLiteDatabase"); + private static final int EVENT_DB_OPERATION = 52000; + private static final int EVENT_DB_CORRUPT = 75004; + private static final String KEY_ENCODING = "UTF-8"; + + private enum SQLiteDatabaseTransactionType { + Deferred, + Immediate, + Exclusive, + } + + // Stores reference to all databases opened in the current process. + // (The referent Object is not used at this time.) + // INVARIANT: Guarded by sActiveDatabases. + private static WeakHashMap sActiveDatabases = + new WeakHashMap(); + + public int status(int operation, boolean reset){ + return native_status(operation, reset); + } //native calls todo + + /** + * Change the password of the open database using sqlite3_rekey(). + * + * @param password new database password + * + * @throws SQLiteException if there is an issue changing the password internally + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + */ + public void changePassword(String password) throws SQLiteException { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + if (password != null) { + byte[] keyMaterial = getBytes(password.toCharArray()); + rekey(keyMaterial); + Arrays.fill(keyMaterial, (byte) 0); + } + } + + /** + * Change the password of the open database using sqlite3_rekey(). + * + * @param password new database password (char array) + * + * @throws SQLiteException if there is an issue changing the password internally + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + */ + public void changePassword(char[] password) throws SQLiteException { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + if (password != null) { + byte[] keyMaterial = getBytes(password); + rekey(keyMaterial); + Arrays.fill(keyMaterial, (byte) 0); + } + } + + private static void loadICUData(Context context, File workingDir) { + OutputStream out = null; + ZipInputStream in = null; + File icuDir = new File(workingDir, "icu"); + File icuDataFile = new File(icuDir, "icudt46l.dat"); + try { + if(!icuDir.exists()) icuDir.mkdirs(); + if(!icuDataFile.exists()) { + in = new ZipInputStream(context.getResourceManager().getRawFileEntry("icudt46l.zip").openRawFile()); + in.getNextEntry(); + out = new FileOutputStream(icuDataFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + } + catch (Exception ex) { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error copying icu dat file", ex); + //} + if(icuDataFile.exists()){ + icuDataFile.delete(); + } + throw new RuntimeException(ex); + } + finally { + try { + if(in != null){ + in.close(); + } + if(out != null){ + out.flush(); + out.close(); + } + } catch (IOException ioe){ + /* if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error in closing streams IO streams after expanding ICU dat file", ioe); + //} + throw new RuntimeException(ioe); + } + } + } + + /** + * Implement this interface to provide custom strategy for loading jni libraries. + */ + public interface LibraryLoader { + /** + * Load jni libraries by given names. + * Straightforward implementation will be calling {@link System#loadLibrary(String name)} + * for every provided library name. + * + * @param libNames library names that sqlcipher need to load + */ + void loadLibraries(String... libNames); + } + + /** + * Loads the native SQLCipher library into the application process. + * @param context context + */ + public static synchronized void loadLibs (Context context) { + loadLibs(context, context.getFilesDir()); + } + + /** + * Loads the native SQLCipher library into the application process. + * @param context context + * @param workingDir file + */ + public static synchronized void loadLibs (Context context, File workingDir) { + loadLibs(context, workingDir, new LibraryLoader() { + @Override + public void loadLibraries(String... libNames) { + for (String libName : libNames) { + System.loadLibrary(libName); + } + } + }); + } + + /** + * Loads the native SQLCipher library into the application process. + * @param context context + * @param libraryLoader LibraryLoader + */ + public static synchronized void loadLibs(Context context, LibraryLoader libraryLoader) { + loadLibs(context, context.getFilesDir(), libraryLoader); + } + + /** + * Loads the native SQLCipher library into the application process. + * @param context context + * @param workingDir file + * @param libraryLoader LibraryLoader + */ + public static synchronized void loadLibs (Context context, File workingDir, LibraryLoader libraryLoader) { + libraryLoader.loadLibraries("sqlcipher"); + } + + /** + * Algorithms used in ON CONFLICT clause + * http://www.sqlite.org/lang_conflict.html + */ + /** + * When a constraint violation occurs, an immediate ROLLBACK occurs, + * thus ending the current transaction, and the command aborts with a + * return code of SQLITE_CONSTRAINT. If no transaction is active + * (other than the implied transaction that is created on every command) + * then this algorithm works the same as ABORT. + */ + public static final int CONFLICT_ROLLBACK = 1; + + /** + * When a constraint violation occurs,no ROLLBACK is executed + * so changes from prior commands within the same transaction + * are preserved. This is the default behavior. + */ + public static final int CONFLICT_ABORT = 2; + + /** + * When a constraint violation occurs, the command aborts with a return + * code SQLITE_CONSTRAINT. But any changes to the database that + * the command made prior to encountering the constraint violation + * are preserved and are not backed out. + */ + public static final int CONFLICT_FAIL = 3; + + /** + * When a constraint violation occurs, the one row that contains + * the constraint violation is not inserted or changed. + * But the command continues executing normally. Other rows before and + * after the row that contained the constraint violation continue to be + * inserted or updated normally. No error is returned. + */ + public static final int CONFLICT_IGNORE = 4; + + /** + * When a UNIQUE constraint violation occurs, the pre-existing rows that + * are causing the constraint violation are removed prior to inserting + * or updating the current row. Thus the insert or update always occurs. + * The command continues executing normally. No error is returned. + * If a NOT NULL constraint violation occurs, the NULL value is replaced + * by the default value for that column. If the column has no default + * value, then the ABORT algorithm is used. If a CHECK constraint + * violation occurs then the IGNORE algorithm is used. When this conflict + * resolution strategy deletes rows in order to satisfy a constraint, + * it does not invoke delete triggers on those rows. + * This behavior might change in a future release. + */ + public static final int CONFLICT_REPLACE = 5; + + /** + * use the following when no conflict action is specified. + */ + public static final int CONFLICT_NONE = 0; + private static final String[] CONFLICT_VALUES = new String[] + {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + + /** + * Maximum Length Of A LIKE Or GLOB Pattern + * The pattern matching algorithm used in the default LIKE and GLOB implementation + * of SQLite can exhibit O(N^2) performance (where N is the number of characters in + * the pattern) for certain pathological cases. To avoid denial-of-service attacks + * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes. + * The default value of this limit is 50000. A modern workstation can evaluate + * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly. + * The denial of service problem only comes into play when the pattern length gets + * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns + * are at most a few dozen bytes in length, paranoid application developers may + * want to reduce this parameter to something in the range of a few hundred + * if they know that external users are able to generate arbitrary patterns. + */ + public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; + + /** + * Flag for {@link #openDatabase} to open the database for reading and writing. + * If the disk is full, this may fail even before you actually write anything. + * + * {@more} Note that the value of this flag is 0, so it is the default. + */ + public static final int OPEN_READWRITE = 0x00000000; // update native code if changing + + /** + * Flag for {@link #openDatabase} to open the database for reading only. + * This is the only reliable way to open a database if the disk may be full. + */ + public static final int OPEN_READONLY = 0x00000001; // update native code if changing + + private static final int OPEN_READ_MASK = 0x00000001; // update native code if changing + + /** + * Flag for {@link #openDatabase} to open the database without support for localized collators. + * + * {@more} This causes the collator LOCALIZED not to be created. + * You must be consistent when using this flag to use the setting the database was + * created with. If this is set, {@link #setLocale} will do nothing. + */ + public static final int NO_LOCALIZED_COLLATORS = 0x00000010; // update native code if changing + + /** + * Flag for {@link #openDatabase} to create the database file if it does not already exist. + */ + public static final int CREATE_IF_NECESSARY = 0x10000000; // update native code if changing + + /** + * SQLite memory database name + */ + public static final String MEMORY = ":memory:"; + + /** + * Indicates whether the most-recently started transaction has been marked as successful. + */ + private boolean mInnerTransactionIsSuccessful; + + /** + * Valid during the life of a transaction, and indicates whether the entire transaction (the + * outer one and all of the inner ones) so far has been successful. + */ + private boolean mTransactionIsSuccessful; + + /** + * Valid during the life of a transaction. + */ + private SQLiteTransactionListener mTransactionListener; //todo + + /** Synchronize on this when accessing the database */ + private final ReentrantLock mLock = new ReentrantLock(true); + + private long mLockAcquiredWallTime = 0L; + private long mLockAcquiredThreadTime = 0L; + + // limit the frequency of complaints about each database to one within 20 sec + // unless run command adb shell setprop log.tag.Database VERBOSE + private static final int LOCK_WARNING_WINDOW_IN_MS = 20000; + /** If the lock is held this long then a warning will be printed when it is released. */ + private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300; + private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100; + private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000; + + private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000; + + // The pattern we remove from database filenames before + // potentially logging them. + private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); + + private long mLastLockMessageTime = 0L; + + // Things related to query logging/sampling for debugging + // slow/frequent queries during development. Always log queries + // which take (by default) 500ms+; shorter queries are sampled + // accordingly. Commit statements, which are typically slow, are + // logged together with the most recently executed SQL statement, + // for disambiguation. The 500ms value is configurable via a + // SystemProperty, but developers actively debugging database I/O + // should probably use the regular log tunable, + // LOG_SLOW_QUERIES_PROPERTY, defined below. + private static int sQueryLogTimeInMillis = 0; // lazily initialized + private static final int QUERY_LOG_SQL_LENGTH = 64; + private static final String COMMIT_SQL = "COMMIT;"; + private String mLastSqlStatement = null; + + // String prefix for slow database query EventLog records that show + // lock acquistions of the database. + /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:"; + + /** Used by native code, do not rename */ + /* package */ long mNativeHandle = 0; + + /** Used to make temp table names unique */ + /* package */ int mTempTableSequence = 0; + + /** The path for the database file */ + private String mPath; + + /** The anonymized path for the database file for logging purposes */ + private String mPathForLogs = null; // lazily populated + + /** The flags passed to open/create */ + private int mFlags; + + /** The optional factory to use when creating new Cursors */ + private CursorFactory mFactory; + + private WeakHashMap mPrograms; //todo + + /** + * for each instance of this class, a cache is maintained to store + * the compiled query statement ids returned by sqlite database. + * key = sql statement with "?" for bind args + * value = {@link SQLiteCompiledSql} + * If an application opens the database and keeps it open during its entire life, then + * there will not be an overhead of compilation of sql statements by sqlite. + * + * why is this cache NOT static? because sqlite attaches compiledsql statements to the + * struct created when {@link SQLiteDatabase#(String, CursorFactory, int)} is + * invoked. + * + * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method + * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because + * most of the apps don't use "?" syntax in their sql, caching is not useful for them. + */ + /* package */ Map mCompiledQueries = new HashMap(); + /** + * @hide + */ + public static final int MAX_SQL_CACHE_SIZE = 250; + private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance + private int mCacheFullWarnings; + private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1; + + /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors + * Corruption + * */ + private final DatabaseErrorHandler mErrorHandler; + + /** maintain stats about number of cache hits and misses */ + private int mNumCacheHits; + private int mNumCacheMisses; + + /** the following 2 members maintain the time when a database is opened and closed */ + private String mTimeOpened = null; + private String mTimeClosed = null; + + /** Used to find out where this object was created in case it never got closed. */ + private Throwable mStackTrace = null; + + // System property that enables logging of slow queries. Specify the threshold in ms. + private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold"; + private final int mSlowQueryThreshold; + + /** + * addSQLiteClosable + * @param closable SQLiteClosable + */ + void addSQLiteClosable(SQLiteClosable closable) { + lock(); + try { + mPrograms.put(closable, null); + } finally { + unlock(); + } + } + + void removeSQLiteClosable(SQLiteClosable closable) { + lock(); + try { + mPrograms.remove(closable); + } finally { + unlock(); + } + } + + @Override + protected void onAllReferencesReleased() { + if (isOpen()) { + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } + dbclose(); + + synchronized (sActiveDatabases) { + sActiveDatabases.remove(this); + } + } + } + + /** + * Attempts to release memory that SQLite holds but does not require to + * operate properly. Typically this memory will come from the page cache. + * + * @return the number of bytes actually released + */ + static public native int releaseMemory(); + + /** + * Control whether or not the SQLiteDatabase is made thread-safe by using locks + * around critical sections. This is pretty expensive, so if you know that your + * DB will only be used by a single thread then you should set this to false. + * The default is true. + * @param lockingEnabled set to true to enable locks, false otherwise + */ + public void setLockingEnabled(boolean lockingEnabled) { + mLockingEnabled = lockingEnabled; + } + + /** + * If set then the SQLiteDatabase is made thread-safe by using locks + * around critical sections + */ + private boolean mLockingEnabled = true; + + /* package */ + void onCorruption() { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Calling error handler for corrupt database (detected) ",mPath); + // } + + // NOTE: DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database + mErrorHandler.onCorruption(this); + } + + /** + * Locks the database for exclusive access. The database lock must be held when + * touch the native sqlite3* object since it is single threaded and uses + * a polling lock contention algorithm. The lock is recursive, and may be acquired + * multiple times by the same thread. This is a no-op if mLockingEnabled is false. + * + * @see #unlock() + */ + /* package */ void lock() { + if (!mLockingEnabled) return; + mLock.lock(); + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + // Use elapsed real-time since the CPU may sleep when waiting for IO + mLockAcquiredWallTime = Time.getRealTime(); + mLockAcquiredThreadTime = Debug.getCpuTime(); + } + } + } + + /** + * Locks the database for exclusive access. The database lock must be held when + * touch the native sqlite3* object since it is single threaded and uses + * a polling lock contention algorithm. The lock is recursive, and may be acquired + * multiple times by the same thread. + * + * @see #unlockForced() + */ + private void lockForced() { + mLock.lock(); + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + // Use elapsed real-time since the CPU may sleep when waiting for IO + mLockAcquiredWallTime = Time.getRealTime(); + mLockAcquiredThreadTime = Debug.getCpuTime(); + } + } + } + + /** + * Releases the database lock. This is a no-op if mLockingEnabled is false. + * + * @see #unlock() + */ + /* package */ void unlock() { + if (!mLockingEnabled) return; + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + checkLockHoldTime(); + } + } + mLock.unlock(); + } + + /** + * Releases the database lock. + * + * @see #unlockForced() + */ + private void unlockForced() { + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + checkLockHoldTime(); + } + } + mLock.unlock(); + } + + private void checkLockHoldTime() { + // Use elapsed real-time since the CPU may sleep when waiting for IO + long elapsedTime = Time.getRealTime(); + long lockedTime = elapsedTime - mLockAcquiredWallTime; + if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT && + !HiLog.isLoggable(0x00204,"SQLitedatabase", HiLog.LOG_APP) && + (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) { //todo, kept LOGapp istead of verbose + return; + } + if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) { + int threadTime = (int) + ((Debug.getCpuTime() - mLockAcquiredThreadTime) / 1000000); + if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS || + lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) { + mLastLockMessageTime = elapsedTime; + String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was " + + threadTime + "ms"; + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) { + /*if(BuildConfig.DEBUG){*/ + HiLog.debug(label, msg, new Exception()); + //} + } else { + /*if(BuildConfig.DEBUG){*/ + HiLog.debug(label, msg); + // } + } + } + } + } + + /** + * Performs a PRAGMA integrity_check; command against the database. + * @return true if the integrity check is ok, otherwise false + */ + public boolean isDatabaseIntegrityOk() { + Pair result = getResultFromPragma("PRAGMA integrity_check;"); + return result.f ? result.s.equals("ok") : result.f; + } + + /** + * Returns a list of attached databases including the main database + * by executing PRAGMA database_list + * @return a list of pairs of database name and filename + */ + public List> getAttachedDbs() { + return getAttachedDbs(this); + } + + /** + * Sets the journal mode of the database to WAL + * @return true if successful, false otherwise + */ + public boolean enableWriteAheadLogging() { + if(inTransaction()) { + String message = "Write Ahead Logging cannot be enabled while in a transaction"; + throw new IllegalStateException(message); + } + List> attachedDbs = getAttachedDbs(this); + if(attachedDbs != null && attachedDbs.size() > 1) return false; + if(isReadOnly() || getPath().equals(MEMORY)) return false; + String command = "PRAGMA journal_mode = WAL;"; + rawExecSQL(command); + return true; + } + + /** + * Sets the journal mode of the database to DELETE (the default mode) + */ + public void disableWriteAheadLogging() { + if(inTransaction()) { + String message = "Write Ahead Logging cannot be disabled while in a transaction"; + throw new IllegalStateException(message); + } + String command = "PRAGMA journal_mode = DELETE;"; + rawExecSQL(command); + } + + /** + * return true if the journal mode is set to WAL, otherwise false + * @return true if the journal mode is set to WAL, otherwise false + */ + public boolean isWriteAheadLoggingEnabled() { + Pair result = getResultFromPragma("PRAGMA journal_mode;"); + return result.f ? result.s.equals("wal") : result.f; + } + + /** + * Enables or disables foreign key constraints + * @param enable used to determine whether or not foreign key constraints are on + */ + public void setForeignKeyConstraintsEnabled(boolean enable) { + if(inTransaction()) { + String message = "Foreign key constraints may not be changed while in a transaction"; + throw new IllegalStateException(message); + } + String command = String.format("PRAGMA foreign_keys = %s;", + enable ? "ON" : "OFF"); + execSQL(command); + } + + /** + * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of + * the work done in that transaction and all of the nested transactions will be committed or + * rolled back. The changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. + * + *

Here is the standard idiom for transactions: + * + *

+     *   db.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ * + * @throws IllegalStateException if the database is not open + */ + public void beginTransaction() { + beginTransactionWithListener((SQLiteTransactionListener)null /* transactionStatusCallback */); + } + + /** + * Begins a transaction in Exlcusive mode. Transactions can be nested. When + * the outer transaction is ended all of the work done in that transaction + * and all of the nested transactions will be committed or rolled back. The + * changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they + * will be committed. + * + *

Here is the standard idiom for transactions: + * + *

+     *   db.beginTransactionWithListener(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ * @param transactionListener listener that should be notified when the transaction begins, + * commits, or is rolled back, either explicitly or by a call to + * {@link #yieldIfContendedSafely}. + * + * @throws IllegalStateException if the database is not open + */ + public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { + beginTransactionWithListenerInternal(transactionListener, + SQLiteDatabaseTransactionType.Exclusive); + } + + /** + * Begins a transaction in Immediate mode + */ + public void beginTransactionNonExclusive() { + beginTransactionWithListenerInternal(null, + SQLiteDatabaseTransactionType.Immediate); + } + + /** + * Begins a transaction in Immediate mode + * @param transactionListener is the listener used to report transaction events + */ + public void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener) { + beginTransactionWithListenerInternal(transactionListener, + SQLiteDatabaseTransactionType.Immediate); + } + + /** + * End a transaction. See beginTransaction for notes about how to use this and when transactions + * are committed and rolled back. + * + * @throws IllegalStateException if the database is not open or is not locked by the current thread + */ + public void endTransaction() { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("no transaction pending"); + } + try { + if (mInnerTransactionIsSuccessful) { + mInnerTransactionIsSuccessful = false; + } else { + mTransactionIsSuccessful = false; + } + if (mLock.getHoldCount() != 1) { + return; + } + RuntimeException savedException = null; + if (mTransactionListener != null) { + try { + if (mTransactionIsSuccessful) { + mTransactionListener.onCommit(); + } else { + mTransactionListener.onRollback(); + } + } catch (RuntimeException e) { + savedException = e; + mTransactionIsSuccessful = false; + } + } + if (mTransactionIsSuccessful) { + execSQL(COMMIT_SQL); + } else { + try { + execSQL("ROLLBACK;"); + if (savedException != null) { + throw savedException; + } + } catch (SQLException e) { + /* if(BuildConfig.DEBUG){*/ + HiLog.debug(label, "exception during rollback, maybe the DB previously " + + "performed an auto-rollback"); + // } + } + } + } finally { + mTransactionListener = null; + unlockForced(); + /*if(BuildConfig.DEBUG){*/ + HiLog.debug(label, "unlocked " + Thread.currentThread() + + ", holdCount is " + mLock.getHoldCount()); + // } + } + } + + /** + * Marks the current transaction as successful. Do not do any more database work between + * calling this and calling endTransaction. Do as little non-database work as possible in that + * situation too. If any errors are encountered between this and endTransaction the transaction + * will still be committed. + * + * @throws IllegalStateException if the database is not open, the current thread is not in a transaction, + * or the transaction is already marked as successful. + */ + public void setTransactionSuccessful() { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("no transaction pending"); + } + if (mInnerTransactionIsSuccessful) { + throw new IllegalStateException( + "setTransactionSuccessful may only be called once per call to beginTransaction"); + } + mInnerTransactionIsSuccessful = true; + } + + /** + * return true if there is a transaction pending + * @return true/falses + */ + public boolean inTransaction() { + return mLock.getHoldCount() > 0; + } + + /** + * Checks if the database lock is held by this thread. + * + * @return true, if this thread is holding the database lock. + */ + public boolean isDbLockedByCurrentThread() { + return mLock.isHeldByCurrentThread(); + } + + /** + * Checks if the database is locked by another thread. This is + * just an estimate, since this status can change at any time, + * including after the call is made but before the result has + * been acted upon. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + */ + public boolean isDbLockedByOtherThreads() { + return !mLock.isHeldByCurrentThread() && mLock.isLocked(); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. + * + * @return true if the transaction was yielded + * + * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock + * will not be yielded. Use yieldIfContendedSafely instead. + */ + @Deprecated + public boolean yieldIfContended() { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(false /* do not check yielding */, + -1 /* sleepAfterYieldDelay */); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + */ + public boolean yieldIfContendedSafely() { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(true /* check yielding */, -1 /* sleepAfterYieldDelay*/); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if + * the lock was actually yielded. This will allow other background threads to make some + * more progress than they would if we started the transaction immediately. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + * + * @throws IllegalStateException if the database is locked more than once by the current thread + * @throws InterruptedException if the thread was interrupted while sleeping + */ + public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(true /* check yielding */, sleepAfterYieldDelay); + } + + private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) { + if (mLock.getQueueLength() == 0) { + // Reset the lock acquire time since we know that the thread was willing to yield + // the lock at this time. + mLockAcquiredWallTime = Time.getRealTime(); + mLockAcquiredThreadTime = Debug.getCpuTime(); + return false; + } + setTransactionSuccessful(); + SQLiteTransactionListener transactionListener = mTransactionListener; + endTransaction(); + if (checkFullyYielded) { + if (this.isDbLockedByCurrentThread()) { + throw new IllegalStateException( + "Db locked more than once. yielfIfContended cannot yield"); + } + } + if (sleepAfterYieldDelay > 0) { + // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to + // check if anyone is using the database. If the database is not contended, + // retake the lock and return. + long remainingDelay = sleepAfterYieldDelay; + while (remainingDelay > 0) { + try { + Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ? + remainingDelay : SLEEP_AFTER_YIELD_QUANTUM); + } catch (InterruptedException e) { + Thread.interrupted(); + } + remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM; + if (mLock.getQueueLength() == 0) { + break; + } + } + } + beginTransactionWithListener(transactionListener); + return true; + } + + /** Maps table names to info about what to which _sync_time column to set + * to NULL on an update. This is used to support syncing. */ + private final Map mSyncUpdateInfo = + new HashMap(); + + public Map getSyncedTables() { + synchronized(mSyncUpdateInfo) { + HashMap tables = new HashMap(); + for (String table : mSyncUpdateInfo.keySet()) { + SyncUpdateInfo info = mSyncUpdateInfo.get(table); + if (info.deletedTable != null) { + tables.put(table, info.deletedTable); + } + } + return tables; + } + } + + /** + * Internal class used to keep track what needs to be marked as changed + * when an update occurs. This is used for syncing, so the sync engine + * knows what data has been updated locally. + */ + static private class SyncUpdateInfo { + /** + * Creates the SyncUpdateInfo class. + * + * @param masterTable The table to set _sync_time to NULL in + * @param deletedTable The deleted table that corresponds to the + * master table + * @param foreignKey The key that refers to the primary key in table + */ + SyncUpdateInfo(String masterTable, String deletedTable, + String foreignKey) { + this.masterTable = masterTable; + this.deletedTable = deletedTable; + this.foreignKey = foreignKey; + } + + /** The table containing the _sync_time column */ + String masterTable; + + /** The deleted table that corresponds to the master table */ + String deletedTable; + + /** The key in the local table the row in table. It may be _id, if table + * is the local table. */ + String foreignKey; + } + + /** + * Used to allow returning sub-classes of {@link ResultSet} when calling query. + */ + public interface CursorFactory { + /** + * See + * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver, + * String, SQLiteQuery)}. + * @param db database + * @param masterQuery SQLiteCursorDriver + * @param editTable string + * @param query query + * @return Resultset + */ + ResultSet newCursor(SQLiteDatabase db, + SQLiteCursorDriver masterQuery, String editTable, + SQLiteQuery query); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) { + return openDatabase(path, password, factory, flags, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags) { + return openDatabase(path, password, factory, flags, null, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) { + return openDatabase(path, password, factory, flags, hook, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) { + return openDatabase(path, password, factory, flags, hook, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + return openDatabase(path, password == null ? null : password.toCharArray(), factory, flags, hook, errorHandler); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + byte[] keyMaterial = getBytes(password); + return openDatabase(path, keyMaterial, factory, flags, hook, errorHandler); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (byte array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, byte[] password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + SQLiteDatabase sqliteDatabase = null; + DatabaseErrorHandler myErrorHandler = (errorHandler != null) ? errorHandler : new DefaultDatabaseErrorHandler(); + + try { + // Open the database. + sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler); + sqliteDatabase.openDatabaseInternal(password, hook); + } catch (SQLiteDatabaseCorruptException e) { + // Try to recover from this, if possible. + // FUTURE TBD: should we consider this for other open failures? + + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Calling error handler for corrupt database " + path, e); + // } + + // NOTE: if this errorHandler.onCorruption() throws the exception _should_ + // bubble back to the original caller. + // DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database + myErrorHandler.onCorruption(sqliteDatabase); + + // try *once* again: + sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler); + sqliteDatabase.openDatabaseInternal(password, hook); + } + + if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { + sqliteDatabase.enableSqlTracing(path); + } + if (SQLiteDebug.DEBUG_SQL_TIME) { + sqliteDatabase.enableSqlProfiling(path); + } + + synchronized (sActiveDatabases) { + sActiveDatabases.put(sqliteDatabase, null); + } + + return sqliteDatabase; + } + + /** + * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY, databaseHook). + * @param file path + * @param password password + * @param factory factory + * @param databaseHook SQLiteDatabaseHook + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openOrCreateDatabase(file, password, factory, databaseHook, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook). + * @param file path + * @param password password + * @param factory factory + * @param databaseHook SQLiteDatabaseHook + * @param errorHandler DatabaseErrorHandler + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openOrCreateDatabase(file == null ? null : file.getPath(), password, factory, databaseHook, errorHandler); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook). + * @param path path + * @param password password + * @param factory factory + * @param databaseHook SQLiteDatabaseHook + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, null); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + /** + * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY). + * @param file file + * @param password password + * @param factory factory + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) { + return openOrCreateDatabase(file, password, factory, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + * @param path path + * @param password password + * @param factory factory + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + * @param path path + * @param password password + * @param factory factory + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + * @param path path + * @param password password + * @param factory CursorFactory + * @return SQLiteDatabase + */ + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null, null); + } + + /** + * Create a memory backed SQLite database. Its contents will be destroyed + * when the database is closed. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called + * @param password to use to open and/or create database file + * + * @return a SQLiteDatabase object, or null if the database can't be created + * + * @throws SQLiteException if the database cannot be opened + */ + public static SQLiteDatabase create(CursorFactory factory, String password) { + // This is a magic string with special meaning for SQLite. + return openDatabase(MEMORY, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY); + } + + /** + * Create a memory backed SQLite database. Its contents will be destroyed + * when the database is closed. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called + * @param password to use to open and/or create database file (char array) + * + * @return a SQLiteDatabase object, or null if the database can't be created + * + * @throws SQLiteException if the database cannot be opened + */ + public static SQLiteDatabase create(CursorFactory factory, char[] password) { + return openDatabase(MEMORY, password, factory, CREATE_IF_NECESSARY); + } + + + /** + * Close the database. + */ + public void close() { + + if (!isOpen()) { + return; // already closed + } + lock(); + try { + closeClosable(); + // close this database instance - regardless of its reference count value + onAllReferencesReleased(); + } finally { + unlock(); + } + } + + private void closeClosable() { + /* deallocate all compiled sql statement objects from mCompiledQueries cache. + * this should be done before de-referencing all {@link SQLiteClosable} objects + * from this database object because calling + * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database + * to be closed. sqlite doesn't let a database close if there are + * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries. + */ + deallocCachedSqlStatements(); + + Iterator> iter = mPrograms.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + SQLiteClosable program = entry.getKey(); + if (program != null) { + program.onAllReferencesReleasedFromContainer(); + } + } + } + + /** + * Native call to close the database. + */ + private native void dbclose(); + + /** + * Gets the database version. + * + * @return the database version + * + * @throws IllegalStateException if the database is not open + */ + public int getVersion() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, "PRAGMA user_version;"); + long version = prog.simpleQueryForLong(); + return (int) version; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the database version. + * + * @param version the new database version + * + * @throws SQLiteException if there is an issue executing the sql internally + * @throws IllegalStateException if the database is not open + */ + public void setVersion(int version) { + execSQL("PRAGMA user_version = " + version); + } + + /** + * Returns the maximum size the database may grow to. + * + * @return the new maximum database size + */ + public long getMaximumSize() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, + "PRAGMA max_page_count;"); + long pageCount = prog.simpleQueryForLong(); + return pageCount * getPageSize(); + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the maximum size the database will grow to. The maximum size cannot + * be set below the current size. + * + * @param numBytes the maximum database size, in bytes + * @return the new maximum database size + */ + public long setMaximumSize(long numBytes) { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long pageSize = getPageSize(); + long numPages = numBytes / pageSize; + // If numBytes isn't a multiple of pageSize, bump up a page + if ((numBytes % pageSize) != 0) { + numPages++; + } + prog = new SQLiteStatement(this, + "PRAGMA max_page_count = " + numPages); + long newPageCount = prog.simpleQueryForLong(); + return newPageCount * pageSize; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Returns the current database page size, in bytes. + * + * @return the database page size, in bytes + */ + public long getPageSize() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, + "PRAGMA page_size;"); + long size = prog.simpleQueryForLong(); + return size; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the database page size. The page size must be a power of two. This + * method does not work if any data has been written to the database file, + * and must be called right after the database has been created. + * + * @param numBytes the database page size, in bytes + */ + public void setPageSize(long numBytes) { + execSQL("PRAGMA page_size = " + numBytes); + } + + /** + * Mark this table as syncable. When an update occurs in this table the + * _sync_dirty field will be set to ensure proper syncing operation. + * + * @param table the table to mark as syncable + * @param deletedTable The deleted table that corresponds to the + * syncable table + * + * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + * + * NOTE: This method was deprecated. + */ + public void markTableSyncable(String table, String deletedTable) { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + + markTableSyncable(table, "_id", table, deletedTable); + } + + /** + * Mark this table as syncable, with the _sync_dirty residing in another + * table. When an update occurs in this table the _sync_dirty field of the + * row in updateTable with the _id in foreignKey will be set to + * ensure proper syncing operation. + * + * @param table an update on this table will trigger a sync time removal + * @param foreignKey this is the column in table whose value is an _id in + * updateTable + * @param updateTable this is the table that will have its _sync_dirty + * + * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + * + * NOTE: This method was deprecated. + */ + public void markTableSyncable(String table, String foreignKey, + String updateTable) { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + + markTableSyncable(table, foreignKey, updateTable, null); + } + + /** + * Mark this table as syncable, with the _sync_dirty residing in another + * table. When an update occurs in this table the _sync_dirty field of the + * row in updateTable with the _id in foreignKey will be set to + * ensure proper syncing operation. + * + * @param table an update on this table will trigger a sync time removal + * @param foreignKey this is the column in table whose value is an _id in + * updateTable + * @param updateTable this is the table that will have its _sync_dirty + * @param deletedTable The deleted table that corresponds to the + * updateTable + * + * @throws SQLiteException if there is an issue executing the sql + */ + private void markTableSyncable(String table, String foreignKey, + String updateTable, String deletedTable) { + lock(); + try { + native_execSQL("SELECT _sync_dirty FROM " + updateTable + + " LIMIT 0"); + native_execSQL("SELECT " + foreignKey + " FROM " + table + + " LIMIT 0"); + } finally { + unlock(); + } + + SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable, + foreignKey); + synchronized (mSyncUpdateInfo) { + mSyncUpdateInfo.put(table, info); + } + } + + /** + * Call for each row that is updated in a cursor. + * + * @param table the table the row is in + * @param rowId the row ID of the updated row + */ + /* package */ void rowUpdated(String table, long rowId) { + SyncUpdateInfo info; + synchronized (mSyncUpdateInfo) { + info = mSyncUpdateInfo.get(table); + } + if (info != null) { + execSQL("UPDATE " + info.masterTable + + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey + + " FROM " + table + " WHERE _id=" + rowId + ")"); + } + } + + /** + * Finds the name of the first table, which is editable. + * + * @param tables a list of tables + * @return the first table listed + */ + public static String findEditTable(String tables) { + if (!TextUtils.isEmpty(tables)) { + // find the first word terminated by either a space or a comma + int spacepos = tables.indexOf(' '); + int commapos = tables.indexOf(','); + + if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { + return tables.substring(0, spacepos); + } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { + return tables.substring(0, commapos); + } + return tables; + } else { + throw new IllegalStateException("Invalid tables"); + } + } + + /** + * Compiles an SQL statement into a reusable pre-compiled statement object. + * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the + * statement and fill in those values with {@link SQLiteProgram#bindString} + * and {@link SQLiteProgram#bindLong} each time you want to run the + * statement. Statements may not return result sets larger than 1x1. + * + * @param sql The raw SQL statement, may contain ? for unknown values to be + * bound later. + * + * @return A pre-compiled {@link SQLiteStatement} object. Note that + * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public SQLiteStatement compileStatement(String sql) throws SQLException { + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + return new SQLiteStatement(this, sql); + } finally { + unlock(); + } + } + + /** + * + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see ResultSet + */ + public ResultSet query(boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit); + } + + /** + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return object, which is positioned before the first entry. Note that + * {@link }s are not synchronized, see the documentation for more details. + * + */ + public ResultSet queryWithFactory(CursorFactory cursorFactory, + boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + String sql = SQLiteQueryBuilder.buildQueryString( + distinct, table, columns, selection, groupBy, having, orderBy, limit); + + return rawQueryWithFactory( + cursorFactory, sql, selectionArgs, findEditTable(table)); + } + + /** + * Query the given table, returning a {@link ResultSet} over the result set. + * + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see ResultSet + */ + public ResultSet query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, null /* limit */); + } + + /** + * Query the given table, returning a {@link ResultSet} over the result set. + * + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see ResultSet + */ + public ResultSet query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy, String limit) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, limit); + } + + /** + * Runs the provided SQL and returns a {@link ResultSet} over the result set. + * + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public ResultSet rawQuery(String sql, String[] selectionArgs) { + return rawQueryWithFactory(null, sql, selectionArgs, null); + } + + /** + * Determines the total size in bytes of the query results, and the largest + * single row in bytes for the query. + * + * @param sql the SQL query. The SQL string must a SELECT statement + * @param args the argments to bind to the query + * + * @return A {@link SQLiteQueryStats} based the provided SQL query. + */ + public SQLiteQueryStats getQueryStats(String sql, Object[] args){ + long totalPayload = 0L; + long largestIndividualPayload = 0L; + try { + String query = String.format("CREATE TABLE tempstat AS %s", sql); + execSQL(query, args); + ResultSet cursor = rawQuery("SELECT sum(payload) FROM dbstat WHERE name = tempstat;", new Object[]{}); + if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + cursor.goToFirstRow(); + totalPayload = cursor.getLong(0); + cursor.close(); + cursor = rawQuery("SELECT max(mx_payload) FROM dbstat WHERE name = tempstat;", new Object[]{}); + if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + cursor.goToFirstRow(); + largestIndividualPayload = cursor.getLong(0); + cursor.close(); + execSQL("DROP TABLE tempstat;"); + } catch(SQLiteException ex) { + execSQL("DROP TABLE IF EXISTS tempstat;"); + throw ex; + } + return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + } + + /** + * Runs the provided SQL and returns a {@link ResultSet} over the result set. + * + * @param sql the SQL query. The SQL string must not be ; terminated + * @param args You may include ?s in where clause in the query, + * which will be replaced by the values from args. The + * values will be bound by their type. + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public ResultSet rawQuery(String sql, Object[] args) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long timeStart = 0; + if (/*Config.LOGV || */mSlowQueryThreshold != -1) { + timeStart = System.currentTimeMillis(); + } + SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null); + ResultSet cursor = null; + try { + cursor = driver.query(mFactory, args); + } finally { + if (/*Config.LOGV ||*/ mSlowQueryThreshold != -1) { + // Force query execution + int count = -1; + if (cursor != null) { + count = cursor.getRowCount(); + } + + long duration = System.currentTimeMillis() - timeStart; + + if (/*BuildConfig.DEBUG || */duration >= mSlowQueryThreshold) { + HiLog.error(label, + "query (" + duration + " ms): " + driver.toString() + + ", args are , count is " + count); + } + } + } + return new CrossProcessCursorWrapper(cursor); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param editTable the name of the first table, which is editable + * + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public ResultSet rawQueryWithFactory( + CursorFactory cursorFactory, String sql, String[] selectionArgs, + String editTable) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long timeStart = 0; + + if (/*Config.LOGV || */mSlowQueryThreshold != -1) { + timeStart = System.currentTimeMillis(); + } + + SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable); + + ResultSet cursor = null; + try { + cursor = driver.query( + cursorFactory != null ? cursorFactory : mFactory, + selectionArgs); + } finally { + if (/*Config.LOGV||*/ mSlowQueryThreshold != -1) { + + // Force query execution + int count = -1; + if (cursor != null) { + count = cursor.getRowCount(); + } + + long duration = System.currentTimeMillis() - timeStart; + + if (/*BuildConfig.DEBUG || */duration >= mSlowQueryThreshold) { + HiLog.debug(label, + "query (" + duration + " ms): " + driver.toString() + + ", args are , count is " + count); + } + } + } + return new CrossProcessCursorWrapper(cursor); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * The cursor will read an initial set of rows and the return to the caller. + * It will continue to read in batches and send data changed notifications + * when the later batches are ready. + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param initialRead set the initial count of items to read from the cursor + * @param maxRead set the count of items to read on each iteration after the first + * @return A {@link } object, which is positioned before the first entry. Note that + * {@link }s are not synchronized, see the documentation for more details. + * + * This work is incomplete and not fully tested or reviewed, so currently + * hidden. + * @hide + */ + public ResultSet rawQuery(String sql, String[] selectionArgs, + int initialRead, int maxRead) { + net.sqlcipher.CursorWrapper cursorWrapper = (net.sqlcipher.CursorWrapper)rawQueryWithFactory(null, sql, selectionArgs, null); + ((SQLiteCursor)cursorWrapper.getResultSet()).setLoadStyle(initialRead, maxRead); + return cursorWrapper; + } + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param values this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long insert(String table, String nullColumnHack, ValuesBucket values) { + try { + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); + } catch (SQLException e) { + /* if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error inserting into " + table, e); + // } + return -1; + } + } + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param values this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @throws SQLException + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long insertOrThrow(String table, String nullColumnHack, ValuesBucket values) + throws SQLException { + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); + } + + /** + * Convenience method for replacing a row in the database. + * + * @param table the table in which to replace the row + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this row will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for + * the row. The key + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long replace(String table, String nullColumnHack, ValuesBucket initialValues) { + try { + return insertWithOnConflict(table, nullColumnHack, initialValues, + CONFLICT_REPLACE); + } catch (SQLException e) { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error inserting into " + table, e); + //} + return -1; + } + } + + /** + * Convenience method for replacing a row in the database. + * + * @param table the table in which to replace the row + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this row will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for + * the row. The key + * @throws SQLException + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long replaceOrThrow(String table, String nullColumnHack, + ValuesBucket initialValues) throws SQLException { + return insertWithOnConflict(table, nullColumnHack, initialValues, + CONFLICT_REPLACE); + } + + /** + * General method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @param conflictAlgorithm for insert conflict resolver + * + * @return the row ID of the newly inserted row + * OR the primary key of the existing row if the input param 'conflictAlgorithm' = + * {@link #CONFLICT_IGNORE} + * OR -1 if any error + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public long insertWithOnConflict(String table, String nullColumnHack, + ValuesBucket initialValues, int conflictAlgorithm) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + + // Measurements show most sql lengths <= 152 + StringBuilder sql = new StringBuilder(152); + sql.append("INSERT"); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(" INTO "); + sql.append(table); + // Measurements show most values lengths < 40 + StringBuilder values = new StringBuilder(40); + + Set> entrySet = null; + if (initialValues != null && initialValues.size() > 0) { + entrySet = initialValues.getAll(); + Iterator> entriesIter = entrySet.iterator(); + sql.append('('); + + boolean needSeparator = false; + while (entriesIter.hasNext()) { + if (needSeparator) { + sql.append(", "); + values.append(", "); + } + needSeparator = true; + Map.Entry entry = entriesIter.next(); + sql.append(entry.getKey()); + values.append('?'); + } + + sql.append(')'); + } else { + sql.append("(" + nullColumnHack + ") "); + values.append("NULL"); + } + + sql.append(" VALUES("); + sql.append(values); + sql.append(");"); + + lock(); + SQLiteStatement statement = null; + try { + statement = compileStatement(sql.toString()); + + // Bind the values + if (entrySet != null) { + int size = entrySet.size(); + Iterator> entriesIter = entrySet.iterator(); + for (int i = 0; i < size; i++) { + Map.Entry entry = entriesIter.next(); + DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue()); + + } + } + + // Run the program and then cleanup + statement.execute(); + + long insertedRowId = lastChangeCount() > 0 ? lastInsertRow() : -1; + if (insertedRowId == -1) { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error inserting using into ", table); + //} + } else { +// if (/*BuildConfig.DEBUG &&*/ HiLog.isLoggable(0x00208, "SqliteDatabase", HiLog.DEBUG)) { //CHANGE + HiLog.debug(label, "Inserting row " + insertedRowId + + " from using into " + table); + // } + } + return insertedRowId; + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Convenience method for deleting rows in the database. + * + * @param table the table to delete from + * @param whereClause the optional WHERE clause to apply when deleting. + * Passing null will delete all rows. + * @param whereArgs arguments + * @return the number of rows affected if a whereClause is passed in, 0 + * otherwise. To remove all rows and get a count pass "1" as the + * whereClause. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int delete(String table, String whereClause, String[] whereArgs) { + return delete(table, whereClause, (Object[])whereArgs); + } + + /** + * Convenience method for deleting rows in the database. + * + * @param table the table to delete from + * @param whereClause the optional WHERE clause to apply when deleting. + * Passing null will delete all rows. + * @param whereArgs arguments + * @return the number of rows affected if a whereClause is passed in, 0 + * otherwise. To remove all rows and get a count pass "1" as the + * whereClause. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int delete(String table, String whereClause, Object[] whereArgs) { + SQLiteStatement statement = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement("DELETE FROM " + table + + (!TextUtils.isEmpty(whereClause) + ? " WHERE " + whereClause : "")); + if (whereArgs != null) { + int numArgs = whereArgs.length; + for (int i = 0; i < numArgs; i++) { + DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]); + } + } + statement.execute(); + return lastChangeCount(); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * @param whereArgs arguments + * @return the number of rows affected + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int update(String table, ValuesBucket values, String whereClause, String[] whereArgs) { + return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); + } + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * @param whereArgs arguments + * @param conflictAlgorithm for update conflict resolver + * + * @return the number of rows affected + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int updateWithOnConflict(String table, ValuesBucket values, + String whereClause, String[] whereArgs, int conflictAlgorithm) { + if (values == null || values.size() == 0) { + throw new IllegalArgumentException("Empty values"); + } + + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(table); + sql.append(" SET "); + + Set> entrySet = values.getAll(); + Iterator> entriesIter = entrySet.iterator(); + + while (entriesIter.hasNext()) { + Map.Entry entry = entriesIter.next(); + sql.append(entry.getKey()); + sql.append("=?"); + if (entriesIter.hasNext()) { + sql.append(", "); + } + } + + if (!TextUtils.isEmpty(whereClause)) { + sql.append(" WHERE "); + sql.append(whereClause); + } + SQLiteStatement statement = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement(sql.toString()); + + // Bind the values + int size = entrySet.size(); + entriesIter = entrySet.iterator(); + int bindArg = 1; + for (int i = 0; i < size; i++) { + Map.Entry entry = entriesIter.next(); + DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue()); + bindArg++; + } + + if (whereArgs != null) { + size = whereArgs.length; + for (int i = 0; i < size; i++) { + statement.bindString(bindArg, whereArgs[i]); + bindArg++; + } + } + + // Run the program and then cleanup + statement.execute(); + int numChangedRows = lastChangeCount(); + if (/*BuildConfig.DEBUG &&*/ HiLog.isLoggable(0x00201, "SQLiteDatabase", HiLog.LOG_APP)) { + HiLog.debug(label, "Updated " + numChangedRows + + " rows using and for " + table); + } + return numChangedRows; + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } catch (SQLException e) { + /* if(BuildConfig.DEBUG){*/ + HiLog.error(label, "Error updating using for ", table); + // } + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Execute a single SQL statement that is not a query. For example, CREATE + * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not + * supported. it takes a write lock + * + * @param sql string + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public void execSQL(String sql) throws SQLException { + long timeStart = Time.getRealActiveTime(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + native_execSQL(sql); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + unlock(); + } + } + + public void rawExecSQL(String sql){ + long timeStart = Time.getRealActiveTime(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + native_rawExecSQL(sql); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + unlock(); + } + } + + /** + * Execute a single SQL statement that is not a query. For example, CREATE + * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not + * supported. it takes a write lock, + * + * @param sql + * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public void execSQL(String sql, Object[] bindArgs) throws SQLException { + SQLiteStatement statement = null; + if (bindArgs == null) { + throw new IllegalArgumentException("Empty bindArgs"); + } + long timeStart = Time.getRealActiveTime(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement(sql); + if (bindArgs != null) { + int numArgs = bindArgs.length; + for (int i = 0; i < numArgs; i++) { + DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]); + } + } + statement.execute(); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + @Override + protected void finalize() { + if (isOpen()) { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "close() was never explicitly called on database '%{default}s ' %{default}s", + mPath, mStackTrace); + // } + closeClosable(); + onAllReferencesReleased(); + } + } + + /** + * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path The full path to the database + * @param password to use to open and/or create a database file (char array) + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already + * exists, mFlags will be updated appropriately. + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, null); + } + + /** + * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path The full path to the database + * @param password to use to open and/or create a database file (char array) + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already + * exists, mFlags will be updated appropriately. + * @param databaseHook to run on pre/post key events + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, databaseHook); + } + + public SQLiteDatabase(String path, byte[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, databaseHook); + } + + /** + * Private constructor (without database password) which DOES NOT attempt to open the database. + * + * @param path The full path to the database + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags to control database access mode and other options + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @throws IllegalArgumentException if the database path is null + */ + private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) { + if (path == null) { + throw new IllegalArgumentException("path should not be null"); + } + + mFlags = flags; + mPath = path; + + mSlowQueryThreshold = -1;//SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1); + mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + mFactory = factory; + mPrograms = new WeakHashMap(); + + mErrorHandler = errorHandler; + } + + private void openDatabaseInternal(final char[] password, SQLiteDatabaseHook hook) { + final byte[] keyMaterial = getBytes(password); + openDatabaseInternal(keyMaterial, hook); + } + + private void openDatabaseInternal(final byte[] password, SQLiteDatabaseHook hook) { + boolean shouldCloseConnection = true; + dbopen(mPath, mFlags); + try { + keyDatabase(hook, new Runnable() { + public void run() { + if(password != null && password.length > 0) { + key(password); + } + } + }); + shouldCloseConnection = false; + + } catch(RuntimeException ex) { + + final char[] keyMaterial = getChars(password); + if(containsNull(keyMaterial)) { + keyDatabase(hook, new Runnable() { + public void run() { + if(password != null) { + key_mutf8(keyMaterial); + } + } + }); + if(password != null && password.length > 0) { + rekey(password); + } + shouldCloseConnection = false; + } else { + throw ex; + } + if(keyMaterial != null && keyMaterial.length > 0) { + Arrays.fill(keyMaterial, (char)0); + } + + } finally { + if(shouldCloseConnection) { + dbclose(); + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } + } + } + + } + + private boolean containsNull(char[] data) { + char defaultValue = '\u0000'; + boolean status = false; + if(data != null && data.length > 0) { + for(char datum : data) { + if(datum == defaultValue) { + status = true; + break; + } + } + } + return status; + } + + private void keyDatabase(SQLiteDatabaseHook databaseHook, Runnable keyOperation) { + if(databaseHook != null) { + databaseHook.preKey(this); + } + if(keyOperation != null){ + keyOperation.run(); + } + if(databaseHook != null){ + databaseHook.postKey(this); + } + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeOpened = getTime(); + } + try { + ResultSet cursor = rawQuery("select count(*) from sqlite_master;", new String[]{}); + if(cursor != null){ + cursor.goToFirstRow(); + int count = cursor.getInt(0); + cursor.close(); + } + } catch (RuntimeException e) { + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, e.getMessage(), e); + // } + throw e; + } + } + + private String getTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.US).format(System.currentTimeMillis()); + } + + /** + * return whether the DB is opened as read only. + * @return true if DB is opened as read only + */ + public boolean isReadOnly() { + return (mFlags & OPEN_READ_MASK) == OPEN_READONLY; + } + + /** + * return true if the DB is currently open + * @return true if the DB is currently open (has not been closed) + */ + public boolean isOpen() { + return mNativeHandle != 0; + } + + public boolean needUpgrade(int newVersion) { + /* NOTE: getVersion() will throw if database is not open. */ + return newVersion > getVersion(); + } + + /** + * Getter for the path to the database file. + * + * @return the path to our database file. + */ + public final String getPath() { + return mPath; + } + + /** + * Removes email addresses from database filenames before they're + * logged to the EventLog where otherwise apps could potentially + * read them. + * @return path for logs + */ + private String getPathForLogs() { + if (mPathForLogs != null) { + return mPathForLogs; + } + if (mPath == null) { + return null; + } + if (mPath.indexOf('@') == -1) { + mPathForLogs = mPath; + } else { + mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY"); + } + return mPathForLogs; + } + + /** + * Sets the locale for this database. Does nothing if this database has + * the NO_LOCALIZED_COLLATORS flag set or was opened read only. + * + * @param locale locale + * @throws SQLException if the locale could not be set. The most common reason + * for this is that there is no collator available for the locale you requested. + * In this case the database remains unchanged. + */ + public void setLocale(Locale locale) { + lock(); + try { + native_setLocale(locale.toString(), mFlags); + } finally { + unlock(); + } + } + + /* + * ============================================================================ + * + * The following methods deal with compiled-sql cache + * ============================================================================ + */ + /** + * adds the given sql and its compiled-statement-id-returned-by-sqlite to the + * cache of compiledQueries attached to 'this'. + * + * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql, + * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current + * mapping is NOT replaced with the new mapping). + * @param sql string + * @param compiledStatement compiledStatement + */ + /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE/* && BuildConfig.DEBUG*/) { + HiLog.debug(label, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); + } + return; + } + + SQLiteCompiledSql compiledSql = null; + synchronized(mCompiledQueries) { + // don't insert the new mapping if a mapping already exists + compiledSql = mCompiledQueries.get(sql); + if (compiledSql != null) { + return; + } + // add this to the cache + if (mCompiledQueries.size() == mMaxSqlCacheSize) { + /* + * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. + * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times + * chances are it is NOT using ? for bindargs - so caching is useless. + * TODO: either let the callers set max cchesize for their app, or intelligently + * figure out what should be cached for a given app. + */ + if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION /*&& BuildConfig.DEBUG*/) { + HiLog.warn(label, "Reached MAX size for compiled-sql statement cache for database " + + getPath() + "; i.e., NO space for this sql statement in cache: " + + sql + ". Please change your sql statements to use '?' for " + + "bindargs, instead of using actual values"); + } + // don't add this entry to cache + } else { + // cache is NOT full. add this to cache. + mCompiledQueries.put(sql, compiledStatement); + if (SQLiteDebug.DEBUG_SQL_CACHE/* && BuildConfig.DEBUG*/) { + HiLog.debug(label, "|adding_sql_to_cache|" + getPath() + "|" + + mCompiledQueries.size() + "|" + sql); + } + } + } + return; + } + + + private void deallocCachedSqlStatements() { + synchronized (mCompiledQueries) { + for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { + compiledSql.releaseSqlStatement(); + } + mCompiledQueries.clear(); + } + } + + /** + * from the compiledQueries cache, returns the compiled-statement-id for the given sql. + * returns null, if not found in the cache. + * @param sql string + * @return compiled-statement-id + */ + /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { + SQLiteCompiledSql compiledStatement = null; + boolean cacheHit; + synchronized(mCompiledQueries) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE)/* && BuildConfig.DEBUG)*/ { + HiLog.debug(label, "|cache NOT found|" + getPath()); + } + return null; + } + cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; + } + if (cacheHit) { + mNumCacheHits++; + } else { + mNumCacheMisses++; + } + + if (SQLiteDebug.DEBUG_SQL_CACHE/* && BuildConfig.DEBUG*/) { + HiLog.debug(label, "|cache_stats|" + + getPath() + "|" + mCompiledQueries.size() + + "|" + mNumCacheHits + "|" + mNumCacheMisses + + "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql); + } + return compiledStatement; + } + + /** + * returns true if the given sql is cached in compiled-sql cache. + * @hide + * @param sql string + * @return true/false + */ + public boolean isInCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + return mCompiledQueries.containsKey(sql); + } + } + + /** + * purges the given sql from the compiled-sql cache. + * @hide + * @param sql string + */ + public void purgeFromCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + mCompiledQueries.remove(sql); + } + } + + /** + * remove everything from the compiled sql cache + * @hide + */ + public void resetCompiledSqlCache() { + synchronized(mCompiledQueries) { + mCompiledQueries.clear(); + } + } + + /** + * return the current maxCacheSqlCacheSize + * @hide + * @return maxCacheSqlCacheSize + */ + public synchronized int getMaxSqlCacheSize() { + return mMaxSqlCacheSize; + } + + /** + * set the max size of the compiled sql cache for this database after purging the cache. + * (size of the cache = number of compiled-sql-statements stored in the cache). + * + * max cache size can ONLY be increased from its current size (default = 0). + * if this method is called with smaller size than the current value of mMaxSqlCacheSize, + * then IllegalStateException is thrown + * + * synchronized because we don't want t threads to change cache size at the same time. + * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) + * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or + * < the value set with previous setMaxSqlCacheSize() call. + * + * @hide + */ + public synchronized void setMaxSqlCacheSize(int cacheSize) { + if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { + throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); + } else if (cacheSize < mMaxSqlCacheSize) { + throw new IllegalStateException("cannot set cacheSize to a value less than the value " + + "set with previous setMaxSqlCacheSize() call."); + } + mMaxSqlCacheSize = cacheSize; + } + + public static byte[] getBytes(char[] data) { + if(data == null || data.length == 0) return null; + CharBuffer charBuffer = CharBuffer.wrap(data); + ByteBuffer byteBuffer = Charset.forName(KEY_ENCODING).encode(charBuffer); + byte[] result = new byte[byteBuffer.limit()]; + byteBuffer.get(result); + return result; + } + + public static char[] getChars(byte[] data){ + if(data == null || data.length == 0) return null; + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + CharBuffer charBuffer = Charset.forName(KEY_ENCODING).decode(byteBuffer); + char[] result = new char[charBuffer.limit()]; + charBuffer.get(result); + return result; + } + + /* begin SQLiteSupportDatabase methods */ + + @Override + public ResultSet query(String query) { + return rawQuery(query, null); + } + + @Override + public ResultSet query(String query, Object[] bindArgs) { + return rawQuery(query, bindArgs); + } + + /* @Override // CancellationSignal not present in hmos + public ResultSet query(SupportSQLiteQuery query) { + return query(query, null); + }*/ + + @Override + public ResultSet query(final SupportSQLiteQuery supportQuery/*, CancellationSignal cancellationSignal*/) {//todo cancellationsignal + String sql = supportQuery.getSql(); + int argumentCount = supportQuery.getArgCount(); + Object[] args = new Object[argumentCount]; + SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null); + SQLiteQuery query = new SQLiteQuery(this, sql, 0, args); + supportQuery.bindTo(query); + return new CrossProcessCursorWrapper(new SQLiteCursor(this, driver, null, query)); + } + + @Override + public long insert(String table, int conflictAlgorithm, + ValuesBucket values) + throws SQLException { + return insertWithOnConflict(table, null, values, conflictAlgorithm); + } + + @Override + public int update(String table, int conflictAlgorithm, ValuesBucket values, + String whereClause, Object[] whereArgs) { + String[] args = new String[whereArgs.length]; + + for (int i = 0; i < whereArgs.length; i++) { + args[i] = whereArgs[i].toString(); + } + + return updateWithOnConflict(table, values, whereClause, args, conflictAlgorithm); + } + + @Override + public void beginTransactionWithListener( + final ohos.data.rdb.TransactionObserver transactionListener) { + beginTransactionWithListener(new SQLiteTransactionListener() { + @Override + public void onBegin() { + transactionListener.onBegin(); + } + + @Override + public void onCommit() { + transactionListener.onCommit(); + } + + @Override + public void onRollback() { + transactionListener.onRollback(); + } + }); + } + + @Override + public void beginTransactionWithListenerNonExclusive( + final ohos.data.rdb.TransactionObserver transactionListener) { + beginTransactionWithListenerNonExclusive( + new SQLiteTransactionListener() { + @Override + public void onBegin() { + transactionListener.onBegin(); + } + + @Override + public void onCommit() { + transactionListener.onCommit(); + } + + @Override + public void onRollback() { + transactionListener.onRollback(); + } + }); + } + + /* end SQLiteSupportDatabase methods */ + + private void beginTransactionWithListenerInternal(SQLiteTransactionListener transactionListener, + SQLiteDatabaseTransactionType transactionType) { + lockForced(); + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + boolean ok = false; + try { + // If this thread already had the lock then get out + if (mLock.getHoldCount() > 1) { + if (mInnerTransactionIsSuccessful) { + String msg = "Cannot call beginTransaction between " + + "calling setTransactionSuccessful and endTransaction"; + IllegalStateException e = new IllegalStateException(msg); + /*if(BuildConfig.DEBUG){*/ + HiLog.error(label, "beginTransaction() failed", e); + // } + throw e; + } + ok = true; + return; + } + // This thread didn't already have the lock, so begin a database + // transaction now. + if(transactionType == SQLiteDatabaseTransactionType.Exclusive) { + execSQL("BEGIN EXCLUSIVE;"); + } else if(transactionType == SQLiteDatabaseTransactionType.Immediate) { + execSQL("BEGIN IMMEDIATE;"); + } else if(transactionType == SQLiteDatabaseTransactionType.Deferred) { + execSQL("BEGIN DEFERRED;"); + } else { + String message = String.format("%s is an unsupported transaction type", + transactionType); + throw new IllegalArgumentException(message); + } + mTransactionListener = transactionListener; + mTransactionIsSuccessful = true; + mInnerTransactionIsSuccessful = false; + if (transactionListener != null) { + try { + transactionListener.onBegin(); + } catch (RuntimeException e) { + execSQL("ROLLBACK;"); + throw e; + } + } + ok = true; + } finally { + if (!ok) { + // beginTransaction is called before the try block so we must release the lock in + // the case of failure. + unlockForced(); + } + } + } + + /** + * this method is used to collect data about ALL open databases in the current process. + * bugreport is a user of this data. + * @return DbStats list + */ + /* package */ static ArrayList getDbStats() { + ArrayList dbStatsList = new ArrayList(); + + for (SQLiteDatabase db : getActiveDatabases()) { + if (db == null || !db.isOpen()) { + continue; + } + + // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db + int lookasideUsed = db.native_getDbLookaside(); + + // get the lastnode of the dbname + String path = db.getPath(); + int indx = path.lastIndexOf("/"); + String lastnode = path.substring((indx != -1) ? ++indx : 0); + + // get list of attached dbs and for each db, get its size and pagesize + ArrayList> attachedDbs = getAttachedDbs(db); + if (attachedDbs == null) { + continue; + } + for (int i = 0; i < attachedDbs.size(); i++) { + Pair p = attachedDbs.get(i); + long pageCount = getPragmaVal(db, p.f + ".page_count;"); + + // first entry in the attached db list is always the main database + // don't worry about prefixing the dbname with "main" + String dbName; + if (i == 0) { + dbName = lastnode; + } else { + // lookaside is only relevant for the main db + lookasideUsed = 0; + dbName = " (attached) " + p.f; + // if the attached db has a path, attach the lastnode from the path to above + if (p.s.trim().length() > 0) { + int idx = p.s.lastIndexOf("/"); + dbName += " : " + p.s.substring((idx != -1) ? ++idx : 0); + } + } + if (pageCount > 0) { + dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), + lookasideUsed)); + } + } + } + return dbStatsList; + } + + private static ArrayList getActiveDatabases() { + ArrayList databases = new ArrayList(); + synchronized (sActiveDatabases) { + databases.addAll(sActiveDatabases.keySet()); + } + return databases; + } + + /** + * get the specified pragma value from sqlite for the specified database. + * only handles pragma's that return int/long. + * NO JAVA locks are held in this method. + * @param db database + * @param pragma string + * @return pragma value + * TODO: use this to do all pragma's in this class + */ + private static long getPragmaVal(SQLiteDatabase db, String pragma) { + if (!db.isOpen()) { + return 0; + } + SQLiteStatement prog = null; + try { + prog = new SQLiteStatement(db, "PRAGMA " + pragma); + long val = prog.simpleQueryForLong(); + return val; + } finally { + if (prog != null) prog.close(); + } + } + + /** + * returns list of full pathnames of all attached databases + * including the main database + * @param dbObj database object + * @return path names list + * TODO: move this to {@link DatabaseUtils} + */ + private static ArrayList> getAttachedDbs(SQLiteDatabase dbObj) { + if (!dbObj.isOpen()) { + return null; + } + ArrayList> attachedDbs = new ArrayList>(); + ResultSet c = dbObj.rawQuery("pragma database_list;", null); + while (c.goToNextRow()) { + attachedDbs.add(new Pair(c.getString(1), c.getString(2))); + } + c.close(); + return attachedDbs; + } + + private Pair getResultFromPragma(String command) { + ResultSet cursor = rawQuery(command, new Object[]{}); + if(cursor == null) return new Pair(false, ""); + cursor.goToFirstRow(); + String value = cursor.getString(0); + cursor.close(); + return new Pair(true, value); + } + + + /** + * Sets the root directory to search for the ICU data file + * @param path path + */ + public static native void setICURoot(String path); + + /** + * Native call to open the database. + * + * @param path The full path to the database + * @param flags flags + */ + private native void dbopen(String path, int flags); + + /** + * Native call to setup tracing of all sql statements + * + * @param path the full path to the database + */ + private native void enableSqlTracing(String path); + + /** + * Native call to setup profiling of all sql statements. + * currently, sqlite's profiling = printing of execution-time + * (wall-clock time) of each of the sql statements, as they + * are executed. + * + * @param path the full path to the database + */ + private native void enableSqlProfiling(String path); + + /** + * Native call to execute a raw SQL statement. {@link #lock} must be held + * when calling this method. + * + * @param sql The raw SQL string + * + * @throws SQLException + */ + /* package */ native void native_execSQL(String sql) throws SQLException; + + /** + * Native call to set the locale. {@link #lock} must be held when calling + * this method. + * @param loc locale + * @param flags flags + * + * @throws SQLException + */ + /* package */ native void native_setLocale(String loc, int flags); + + /** + * Returns the row ID of the last row inserted into the database. + * + * @return the row ID of the last row inserted into the database. + */ + /* package */ native long lastInsertRow(); + + /** + * Returns the number of changes made in the last statement executed. + * + * @return the number of changes made in the last statement executed. + */ + /* package */ native int lastChangeCount(); + + /** + * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here + * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html + * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED + */ + private native int native_getDbLookaside(); + + private native void native_rawExecSQL(String sql); + + private native int native_status(int operation, boolean reset); + + private native void key(byte[] key) throws SQLException; + private native void key_mutf8(char[] key) throws SQLException; + private native void rekey(byte[] key) throws SQLException; +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseCorruptException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseCorruptException.java new file mode 100644 index 0000000000000000000000000000000000000000..2e7373ca02d97d1b3ccc170ea75a097d0902aaca --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseCorruptException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that the SQLite database file is corrupt. + */ +public class SQLiteDatabaseCorruptException extends SQLiteException { + public SQLiteDatabaseCorruptException() {} + + public SQLiteDatabaseCorruptException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java new file mode 100644 index 0000000000000000000000000000000000000000..70d00f6697caab2261dfcfcd5ef57a828ad1efc7 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java @@ -0,0 +1,17 @@ +package net.sqlcipher.database; + +/** + * An interface to perform pre and post key operations against a database. + */ +public interface SQLiteDatabaseHook { + /** + * Called before opening the database for Hos. + * @param database database + */ + void preKey(SQLiteDatabase database); + /** + * Called immediately after opening the database for Hos. + * @param database database + */ + void postKey(SQLiteDatabase database); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java new file mode 100644 index 0000000000000000000000000000000000000000..a17b2b7a47328dd2f4d7d41cc3e7c7767184cd69 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.util.ArrayList; +import ohos.hiviewdfx.HiLog; + +/** + * Provides debugging info about all SQLite databases running in the current process. + * + * {@hide} + */ +public final class SQLiteDebug { + /** + * Controls the printing of SQL statements as they are executed. + */ + public static final boolean DEBUG_SQL_STATEMENTS = + HiLog.isLoggable(0x00201,"SQLiteStatements", HiLog.LOG_APP); + + /** + * Controls the printing of wall-clock time taken to execute SQL statements + * as they are executed. + */ + public static final boolean DEBUG_SQL_TIME = + HiLog.isLoggable(0x00201,"SQLiteTime", HiLog.LOG_APP); + + /** + * Controls the printing of compiled-sql-statement cache stats. + */ + public static final boolean DEBUG_SQL_CACHE = + HiLog.isLoggable(0x00201,"SQLiteCompiledSql", HiLog.LOG_APP); + + /** + * Controls the stack trace reporting of active cursors being + * finalized. + */ + public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION = + HiLog.isLoggable(0x00201,"SQLiteCursorClosing", HiLog.LOG_APP); + + /** + * Controls the tracking of time spent holding the database lock. + */ + public static final boolean DEBUG_LOCK_TIME_TRACKING = + HiLog.isLoggable(0x00201,"SQLiteLockTime", HiLog.LOG_APP); + + /** + * Controls the printing of stack traces when tracking the time spent holding the database lock. + */ + public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE = + HiLog.isLoggable(0x00201,"SQLiteLockStackTrace", HiLog.LOG_APP); + + /** + * Contains statistics about the active pagers in the current process. + * + * @see #getPagerStats(PagerStats) + */ + public static class PagerStats { + /** The total number of bytes in all pagers in the current process + * @deprecated not used any longer + */ + @Deprecated + public long totalBytes; + /** The number of bytes in referenced pages in all pagers in the current process + * @deprecated not used any longer + * */ + @Deprecated + public long referencedBytes; + /** The number of bytes in all database files opened in the current process + * @deprecated not used any longer + */ + @Deprecated + public long databaseBytes; + /** The number of pagers opened in the current process + * @deprecated not used any longer + */ + @Deprecated + public int numPagers; + + /** the current amount of memory checked out by sqlite using sqlite3_malloc(). + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int memoryUsed; + + /** the number of bytes of page cache allocation which could not be sattisfied by the + * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc(). + * The returned value includes allocations that overflowed because they where too large + * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations + * that overflowed because no space was left in the page cache. + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int pageCacheOverflo; + + /** records the largest memory allocation request handed to sqlite3. + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int largestMemAlloc; + + /** a list of {@link DbStats} - one for each main database opened by the applications + * running on the device + */ + public ArrayList dbStats; + } + + /** + * contains statistics about a database + */ + public static class DbStats { + /** name of the database */ + public String dbName; + + /** the page size for the database */ + public long pageSize; + + /** the database size */ + public long dbSize; + + /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */ + public int lookaside; + + public DbStats(String dbName, long pageCount, long pageSize, int lookaside) { + this.dbName = dbName; + this.pageSize = pageSize; + dbSize = (pageCount * pageSize) / 1024; + this.lookaside = lookaside; + } + } + + /** + * return all pager and database stats for the current process. + * @return {@link PagerStats} + */ + public static PagerStats getDatabaseInfo() { + PagerStats stats = new PagerStats(); + getPagerStats(stats); + stats.dbStats = SQLiteDatabase.getDbStats(); + return stats; + } + + /** + * Gathers statistics about all pagers in the current process. + * @param stats PagerStats + */ + public static native void getPagerStats(PagerStats stats); + + /** + * Returns the size of the SQLite heap. + * @return The size of the SQLite heap in bytes. + */ + public static native long getHeapSize(); + + /** + * Returns the amount of allocated memory in the SQLite heap. + * @return The allocated size in bytes. + */ + public static native long getHeapAllocatedSize(); + + /** + * Returns the amount of free memory in the SQLite heap. + * @return The freed size in bytes. + */ + public static native long getHeapFreeSize(); + + /** + * Determines the number of dirty belonging to the SQLite + * heap segments of this process. pages[0] returns the number of + * shared pages, pages[1] returns the number of private pages + * @param pages pagess + */ + public static native void getHeapDirtyPages(int[] pages); + + private static int sNumActiveCursorsFinalized = 0; + + /** + * Returns the number of active cursors that have been finalized. This depends on the GC having + * run but is still useful for tests. + * @return Returns the number of active cursors that have been finalized + */ + public static int getNumActiveCursorsFinalized() { + return sNumActiveCursorsFinalized; + } + + static synchronized void notifyActiveCursorFinalized() { + sNumActiveCursorsFinalized++; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java new file mode 100644 index 0000000000000000000000000000000000000000..40c42e62212d7c168260b51251be430b884d7055 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; +import ohos.data.resultset.ResultSet; + +/** + * A cursor driver that uses the given query directly. + * + * @hide + */ +public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { + private String mEditTable; + private SQLiteDatabase mDatabase; + private ResultSet mCursor; + private String mSql; + private SQLiteQuery mQuery; + + public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) { + mDatabase = db; + mEditTable = editTable; + mSql = sql; + } + + public ResultSet query(CursorFactory factory, Object[] args) { + SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, args); + try { + query.bindArguments(args); + if (factory == null) { + mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query); + } else { + mCursor = factory.newCursor(mDatabase, this, mEditTable, query); + } + mQuery = query; + query = null; + return mCursor; + } finally { + // Make sure this object is cleaned up if something happens + if (query != null) query.close(); + } + } + + public ResultSet query(CursorFactory factory, String[] selectionArgs) { + // Compile the query + SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs); + + try { + // Arg binding + int numArgs = selectionArgs == null ? 0 : selectionArgs.length; + for (int i = 0; i < numArgs; i++) { + query.bindString(i + 1, selectionArgs[i]); + } + + // Create the cursor + if (factory == null) { + mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query); + + } else { + mCursor = factory.newCursor(mDatabase, this, mEditTable, query); + } + + mQuery = query; + query = null; + return mCursor; + } finally { + // Make sure this object is cleaned up if something happens + if (query != null) query.close(); + } + } + + public void cursorClosed() { + mCursor = null; + } + + public void setBindArguments(String[] bindArgs) { + final int numArgs = bindArgs.length; + for (int i = 0; i < numArgs; i++) { + mQuery.bindString(i + 1, bindArgs[i]); + } + } + + @Override + public void cursorDeactivated() { + // Do nothing + } + + @Override + public void cursorRequeried(ResultSet cursor) { + // Do nothing + } + + @Override + public String toString() { + return "SQLiteDirectCursorDriver: " + mSql; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDiskIOException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDiskIOException.java new file mode 100644 index 0000000000000000000000000000000000000000..de4b54319e749400104b37fe3b757414a2ac8a1a --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDiskIOException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that an IO error occured while accessing the + * SQLite database file. + */ +public class SQLiteDiskIOException extends SQLiteException { + public SQLiteDiskIOException() {} + + public SQLiteDiskIOException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDoneException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDoneException.java new file mode 100644 index 0000000000000000000000000000000000000000..f0f6f0dc7c18ad9e129294c213f980a28682e103 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDoneException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that the SQLite program is done. + * Thrown when an operation that expects a row (such as {@link + * SQLiteStatement#simpleQueryForString} or {@link + * SQLiteStatement#simpleQueryForLong}) does not get one. + */ +public class SQLiteDoneException extends SQLiteException { + public SQLiteDoneException() {} + + public SQLiteDoneException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteException.java new file mode 100644 index 0000000000000000000000000000000000000000..2c7f11a37c35fb3c5ddf3110ca35bc2673ce0d43 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.*; + +/** + * A SQLite exception that indicates there was an error with SQL parsing or execution. + */ +public class SQLiteException extends SQLException { + public SQLiteException() {} + + public SQLiteException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteFullException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteFullException.java new file mode 100644 index 0000000000000000000000000000000000000000..66af19fee72525681b661d40aae0bd220c1fca3b --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteFullException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * An exception that indicates that the SQLite database is full. + */ +public class SQLiteFullException extends SQLiteException { + public SQLiteFullException() {} + + public SQLiteFullException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteMisuseException.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteMisuseException.java new file mode 100644 index 0000000000000000000000000000000000000000..ef261fc75b176c7925fc1246b3b050cbc092adbc --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteMisuseException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +public class SQLiteMisuseException extends SQLiteException { + public SQLiteMisuseException() {} + + public SQLiteMisuseException(String error) { + super(error); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..f078f40308d85a445efe793300e3a560aa134067 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.io.File; +import ohos.app.Context; +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.DefaultDatabaseErrorHandler; +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * A helper class to manage database creation and version management. + * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and + * optionally {@link #onOpen}, and this class takes care of opening the database + * if it exists, creating it if it does not, and upgrading it as necessary. + * Transactions are used to make sure the database is always in a sensible state. + *

For an example, see the NotePadProvider class in the NotePad sample application, + * in the samples/ directory of the SDK.

+ */ +public abstract class SQLiteOpenHelper { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00208, "SQLiteOpenHelper"); + + private final Context mContext; + private final String mName; + private final CursorFactory mFactory; + private final int mNewVersion; + private final SQLiteDatabaseHook mHook; + private final DatabaseErrorHandler mErrorHandler; + private boolean mEnableWriteAheadLogging; + private boolean mDeferSetWriteAheadLoggingEnabled; + + private SQLiteDatabase mDatabase = null; + private boolean mIsInitializing = false; + + /** + * Create a helper object to create, open, and/or manage a database. + * This method always returns very quickly. The database is not actually + * created or opened until one of {@link #getWritableDatabase} or + * {@link #getReadableDatabase} is called. + * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { + this(context, name, factory, version, null, new DefaultDatabaseErrorHandler()); + } + + /** + * Create a helper object to create, open, and/or manage a database. + * The database is not actually created or opened until one of + * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. + * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + * @param hook to run on pre/post key events + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, + int version, SQLiteDatabaseHook hook) { + this(context, name, factory, version, hook, new DefaultDatabaseErrorHandler()); + } + + /** + * Create a helper object to create, open, and/or manage a database. + * The database is not actually created or opened until one of + * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. + * + *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be + * used to handle corruption when sqlite reports database corruption.

+ * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + * @param hook to run on pre/post key events + * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption. + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, + int version, SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); + if (errorHandler == null) { + throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null."); + } + + mContext = context; + mName = name; + mFactory = factory; + mNewVersion = version; + mHook = hook; + mErrorHandler = errorHandler; + } + + /** + * Create and/or open a database that will be used for reading and writing. + * Once opened successfully, the database is cached, so you can call this + * method every time you need to write to the database. Make sure to call + * {@link #close} when you no longer need it. + * + *

Errors such as bad permissions or a full disk may cause this operation + * to fail, but future attempts may succeed if the problem is fixed.

+ * + * @param password password + * @throws SQLiteException if the database cannot be opened for writing + * @return a read/write database object valid until {@link #close} is called + */ + + public synchronized SQLiteDatabase getWritableDatabase(String password) { + return getWritableDatabase(password == null ? null : password.toCharArray()); + } + + public synchronized SQLiteDatabase getWritableDatabase(char[] password) { + return getWritableDatabase(password == null ? null : SQLiteDatabase.getBytes(password)); + } + + public synchronized SQLiteDatabase getWritableDatabase(byte[] password) { + if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { + return mDatabase; // The database is already open for business + } + + if (mIsInitializing) { + throw new IllegalStateException("getWritableDatabase called recursively"); + } + + // If we have a read-only database open, someone could be using it + // (though they shouldn't), which would cause a lock to be held on + // the file, and our attempts to open the database read-write would + // fail waiting for the file lock. To prevent that, we acquire the + // lock on the read-only database, which shuts out other users. + + boolean success = false; + SQLiteDatabase db = null; + if (mDatabase != null) mDatabase.lock(); + try { + mIsInitializing = true; + if (mName == null) { + db = SQLiteDatabase.create(null, ""); + } else { + // String path = mContext.getDatabaseDir().getPath(); + File dbPathFile = new File (mName); + if (!dbPathFile.exists()) { + dbPathFile.getParentFile().mkdirs(); + } + db = SQLiteDatabase.openOrCreateDatabase(mName, password, mFactory, mHook, mErrorHandler); + } + if(mDeferSetWriteAheadLoggingEnabled) { + mEnableWriteAheadLogging = db.enableWriteAheadLogging(); + } + onConfigure(db); + int version = db.getVersion(); + if (version != mNewVersion) { + db.beginTransaction(); + try { + if (version == 0) { + onCreate(db); + } else { + if(version > mNewVersion) { + onDowngrade(db, version, mNewVersion); + } else { + onUpgrade(db, version, mNewVersion); + } + } + db.setVersion(mNewVersion); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + onOpen(db); + success = true; + return db; + } finally { + mIsInitializing = false; + if (success) { + if (mDatabase != null) { + try { mDatabase.close(); } catch (Exception e) { } + mDatabase.unlock(); + } + mDatabase = db; + } else { + if (mDatabase != null) mDatabase.unlock(); + if (db != null) db.close(); + } + } + } + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + * @param password password + * @throws SQLiteException if the database cannot be opened + * @return a database object valid until {@link #getWritableDatabase} + * or {@link #close} is called. + */ + public synchronized SQLiteDatabase getReadableDatabase(String password) { + return getReadableDatabase(password == null ? null : password.toCharArray()); + } + + public synchronized SQLiteDatabase getReadableDatabase(char[] password) { + return getReadableDatabase(password == null ? null : SQLiteDatabase.getBytes(password)); + } + + public synchronized SQLiteDatabase getReadableDatabase(byte[] password) { + if (mDatabase != null && mDatabase.isOpen()) { + return mDatabase; // The database is already open for business + } + + if (mIsInitializing) { + throw new IllegalStateException("getReadableDatabase called recursively"); + } + + try { + return getWritableDatabase(password); + } catch (SQLiteException e) { + if (mName == null) throw e; // Can't open a temp database read-only! + HiLog.error(label, "Couldn't open " + mName + " for writing (will try read-only):", e); + } + + SQLiteDatabase db = null; + try { + mIsInitializing = true; + String path = mContext.getDatabaseDir().getPath(); + File databasePath = new File(path); + File databasesDirectory = new File(mContext.getDatabaseDir().getParent()); + + if(!databasesDirectory.exists()){ + databasesDirectory.mkdirs(); + } + if(!databasePath.exists()){ + mIsInitializing = false; + db = getWritableDatabase(password); + mIsInitializing = true; + db.close(); + } + db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY, mHook, mErrorHandler); + if (db.getVersion() != mNewVersion) { + throw new SQLiteException("Can't upgrade read-only database from version " + + db.getVersion() + " to " + mNewVersion + ": " + path); + } + + onOpen(db); + HiLog.warn(label, "Opened " + mName + " in read-only mode"); + mDatabase = db; + return mDatabase; + } finally { + mIsInitializing = false; + if (db != null && db != mDatabase) db.close(); + } + } + + /** + * Close any open database object. + */ + public synchronized void close() { + if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); + + if (mDatabase != null && mDatabase.isOpen()) { + mDatabase.close(); + mDatabase = null; + } + } + + /** + * Return the name of the SQLite database being opened, as given to + * the constructor. + * @return database name + */ + public String getDatabaseName() { + return mName; + } + + /** + * Enables or disables the use of write-ahead logging for the database. + * + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + * @param enabled True if write-ahead logging should be enabled, false if it + * should be disabled. + * + * @see SQLiteDatabase#enableWriteAheadLogging() + */ + public void setWriteAheadLoggingEnabled(boolean enabled) { + synchronized (this) { + if (mEnableWriteAheadLogging != enabled) { + if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { + if (enabled) { + mDatabase.enableWriteAheadLogging(); + } else { + mDatabase.disableWriteAheadLogging(); + } + mEnableWriteAheadLogging = enabled; + } else { + mDeferSetWriteAheadLoggingEnabled = enabled; + } + } + } + } + + /** + * Called when the database needs to be downgraded. This is strictly similar to + * {@link #onUpgrade} method, but is called whenever current version is newer than requested one. + * However, this method is not abstract, so it is not mandatory for a customer to + * implement it. If not overridden, default implementation will reject downgrade and + * throws SQLiteException + * + *

+ * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + *

+ * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + throw new SQLiteException("Can't downgrade database from version " + + oldVersion + " to " + newVersion); + } + + /** + * Called when the database connection is being configured, to enable features + * such as write-ahead logging or foreign key support. + *

+ * This method is called before {@link #onCreate}, {@link #onUpgrade}, + * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify + * the database except to configure the database connection as required. + *

+ * This method should only call methods that configure the parameters of the + * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging} + * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, + * {@link SQLiteDatabase#setLocale}, or executing PRAGMA statements. + *

+ * + * @param db The database. + */ + public void onConfigure(SQLiteDatabase db) {} + + /** + * Called when the database is created for the first time. This is where the + * creation of tables and the initial population of the tables should happen. + * + * @param db The database. + */ + public abstract void onCreate(SQLiteDatabase db); + + /** + * Called when the database needs to be upgraded. The implementation + * should use this method to drop tables, add tables, or do anything else it + * needs to upgrade to the new schema version. + * + *

The SQLite ALTER TABLE documentation can be found + * here. If you add new columns + * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns + * you can use ALTER TABLE to rename the old table, then create the new table and then + * populate the new table with the contents of the old table. + * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); + + /** + * Called when the database has been opened. + * Override method should check {@link SQLiteDatabase#isReadOnly} before + * updating the database. + * + * @param db The database. + */ + public void onOpen(SQLiteDatabase db) {} +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java new file mode 100644 index 0000000000000000000000000000000000000000..61f4cbe7ac7f019993211410d0c08a7836f592a9 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import net.sqlcipher.harmonyx.SupportSQLiteProgram; + +/** + * A base class for compiled SQLite programs. + * + * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple + * threads should perform its own synchronization when using the SQLiteProgram. + */ +public abstract class SQLiteProgram extends SQLiteClosable implements + SupportSQLiteProgram { + + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00205, "SQLiteProgram"); + + /** The database this program is compiled against. + * @deprecated do not use this + */ + @Deprecated + protected SQLiteDatabase mDatabase; + + /** The SQL used to create this query */ + /* package */ final String mSql; + + /** + * Native linkage, do not modify. This comes from the database and should not be modified + * in here or in the native code. + * @deprecated do not use this + */ + @Deprecated + protected long nHandle = 0; + + /** + * the SQLiteCompiledSql object for the given sql statement. + */ + private SQLiteCompiledSql mCompiledSql; + + /** + * SQLiteCompiledSql statement id is populated with the corresponding object from the above + * member. This member is used by the native_bind_* methods + * @deprecated do not use this + */ + @Deprecated + protected long nStatement = 0; + + /** + * Indicates whether {@link #close()} has been called. + */ + boolean mClosed = false; + + /* package */ SQLiteProgram(SQLiteDatabase db, String sql) { + mDatabase = db; + mSql = sql.trim(); + db.acquireReference(); + db.addSQLiteClosable(this); + this.nHandle = db.mNativeHandle; + int crudPrefixLength = 6; + + // only cache CRUD statements + String prefixSql = mSql.length() >= crudPrefixLength ? mSql.substring(0, crudPrefixLength) : mSql; + if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") && + !prefixSql.equalsIgnoreCase("REPLAC") && + !prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) { + mCompiledSql = new SQLiteCompiledSql(db, sql); + nStatement = mCompiledSql.nStatement; + // since it is not in the cache, no need to acquire() it. + return; + } + + // it is not pragma + mCompiledSql = db.getCompiledStatementForSql(sql); + if (mCompiledSql == null) { + // create a new compiled-sql obj + mCompiledSql = new SQLiteCompiledSql(db, sql); + + // add it to the cache of compiled-sqls + // but before adding it and thus making it available for anyone else to use it, + // make sure it is acquired by me. + mCompiledSql.acquire(); + db.addToCompiledQueries(sql, mCompiledSql); + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "Created DbObj (id#" + mCompiledSql.nStatement + + ") for sql: " + sql); + } + } else { + // it is already in compiled-sql cache. + // try to acquire the object. + if (!mCompiledSql.acquire()) { + long last = mCompiledSql.nStatement; + // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object. + // we can't have two different SQLiteProgam objects can't share the same + // CompiledSql object. create a new one. + // finalize it when I am done with it in "this" object. + mCompiledSql = new SQLiteCompiledSql(db, sql); + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + HiLog.debug(label, "** possible bug ** Created NEW DbObj (id#" + + mCompiledSql.nStatement + + ") because the previously created DbObj (id#" + last + + ") was not released for sql:" + sql); + } + // since it is not in the cache, no need to acquire() it. + } + } + nStatement = mCompiledSql.nStatement; + } + + @Override + protected void onAllReferencesReleased() { + releaseCompiledSqlIfNotInCache(); + mDatabase.releaseReference(); + mDatabase.removeSQLiteClosable(this); + } + + @Override + protected void onAllReferencesReleasedFromContainer() { + releaseCompiledSqlIfNotInCache(); + mDatabase.releaseReference(); + } + + private void releaseCompiledSqlIfNotInCache() { + if (mCompiledSql == null) { + return; + } + synchronized(mDatabase.mCompiledQueries) { + if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) { + // it is NOT in compiled-sql cache. i.e., responsibility of + // releasing this statement is on me. + mCompiledSql.releaseSqlStatement(); + mCompiledSql = null; + nStatement = 0; + } else { + // it is in compiled-sql cache. reset its CompiledSql#mInUse flag + mCompiledSql.release(); + } + } + } + + /** + * Returns a unique identifier for this program. + * + * @return a unique identifier for this program + */ + public final long getUniqueId() { + return nStatement; + } + + /* package */ String getSqlString() { + return mSql; + } + + /** + * compile + * @deprecated This method is deprecated and must not be used. + * + * @param sql the SQL string to compile + * @param forceCompilation forces the SQL to be recompiled in the event that there is an + * existing compiled SQL program already around + */ + @Deprecated + protected void compile(String sql, boolean forceCompilation) { + // TODO is there a need for this? + } + + /** + * Bind a NULL value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind null to + */ + @Override + public void bindNull(int index) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_bind_null(index); + } finally { + releaseReference(); + } + } + + /** + * Bind a long value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + @Override + public void bindLong(int index, long value) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_bind_long(index, value); + } finally { + releaseReference(); + } + } + + /** + * Bind a double value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + @Override + public void bindDouble(int index, double value) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_bind_double(index, value); + } finally { + releaseReference(); + } + } + + /** + * Bind a String value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + @Override + public void bindString(int index, String value) { + if (value == null) { + throw new IllegalArgumentException("the bind value at index " + index + " is null"); + } + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_bind_string(index, value); + } finally { + releaseReference(); + } + } + + /** + * Bind a byte array value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + @Override + public void bindBlob(int index, byte[] value) { + if (value == null) { + throw new IllegalArgumentException("the bind value at index " + index + " is null"); + } + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_bind_blob(index, value); + } finally { + releaseReference(); + } + } + + /** + * Clears all existing bindings. Unset bindings are treated as NULL. + */ + @Override + public void clearBindings() { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + acquireReference(); + try { + native_clear_bindings(); + } finally { + releaseReference(); + } + } + + /** + * Release this program's resources, making it invalid. + */ + public void close() { + if (mClosed) { + return; + } + if (!mDatabase.isOpen()) { + return; + } + mDatabase.lock(); + try { + releaseReference(); + } finally { + mDatabase.unlock(); + } + mClosed = true; + } + + /** + * native_compile + * @deprecated This method is deprecated and must not be used. + * Compiles SQL into a SQLite program. + * + *

The database lock must be held when calling this method. + * @param sql The SQL to compile. + */ + @Deprecated + protected final native void native_compile(String sql); + + /** + * native_finalize + * @deprecated This method is deprecated and must not be used. + */ + @Deprecated + protected final native void native_finalize(); + + protected final native void native_bind_null(int index); + protected final native void native_bind_long(int index, long value); + protected final native void native_bind_double(int index, double value); + protected final native void native_bind_string(int index, String value); + protected final native void native_bind_blob(int index, byte[] value); + private final native void native_clear_bindings(); +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..72471903dd58a3f3fee5c66f4fb702ac734b2604 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.*; + +import ohos.miscservices.timeutility.Time; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * A SQLite program that represents a query that reads the resulting rows into a CursorWindow. + * This class is used by SQLiteCursor and isn't useful itself. + * + * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple + * threads should perform its own synchronization when using the SQLiteQuery. + */ +public class SQLiteQuery extends SQLiteProgram { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00206, "SQLiteQuery"); + + /** The index of the unbound OFFSET parameter */ + private int mOffsetIndex; + + /** Args to bind on requery */ + private String[] mBindArgs; + private Object[] mObjectBindArgs; + + /** + * Create a persistent query object. + * + * @param db The database that this query object is associated with + * @param query The SQL string for this query. + * @param offsetIndex The 1-based index to the OFFSET parameter, + */ + /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) { + super(db, query); + + mOffsetIndex = offsetIndex; + mBindArgs = bindArgs; + } + + SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, Object[] bindArgs) { + super(db, query); + mOffsetIndex = offsetIndex; + mObjectBindArgs = bindArgs; + int length = mObjectBindArgs != null ? mObjectBindArgs.length : 0; + mBindArgs = new String[length]; + } + + /** + * Reads rows into a buffer. This method acquires the database lock. + * + * @param window The window to fill into + * @return number of total rows in the query + */ + /* package */ + int fillWindow(CursorWindow window, + int maxRead, int lastPos) { + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + try { + acquireReference(); + try { + window.acquireRef(); + // if the start pos is not equal to 0, then most likely window is + // too small for the data set, loading by another thread + // is not safe in this situation. the native code will ignore maxRead + int numRows = native_fill_window(window, + window.getStartRowIndex(), + window.getRequiredPosition(), + mOffsetIndex, + maxRead, lastPos); //todo native porting + + // Logging + if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { + HiLog.debug(label, "fillWindow(): " + mSql); + } + return numRows; + } catch (IllegalStateException e){ + // simply ignore it + return 0; + } catch (SQLiteDatabaseCorruptException e) { + mDatabase.onCorruption(); + throw e; + } finally { + window.releaseRef(); + } + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + /** + * Get the column count for the statement. Only valid on query based + * statements. The database must be locked + * when calling this method. + * + * @return The number of column in the statement's result set. + */ + /* package */ int columnCountLocked() { + acquireReference(); + try { + return native_column_count(); + } finally { + releaseReference(); + } + } + + /** + * Retrieves the column name for the given column index. The database must be locked + * when calling this method. + * + * @param columnIndex the index of the column to get the name for + * @return The requested column's name + */ + /* package */ String columnNameLocked(int columnIndex) { + acquireReference(); + try { + return native_column_name(columnIndex); + } finally { + releaseReference(); + } + } + + @Override + public String toString() { + return "SQLiteQuery: " + mSql; + } + + /** + * Called by SQLiteCursor when it is requeried. + */ + /* package */ void requery() { + if (mBindArgs != null) { + int len = mBindArgs.length; + try { + if(mObjectBindArgs != null) { + bindArguments(mObjectBindArgs); + } else { + for (int i = 0; i < len; i++) { + super.bindString(i + 1, mBindArgs[i]); + } + } + } catch (SQLiteMisuseException e) { + StringBuilder errMsg = new StringBuilder("mSql " + mSql); + for (int i = 0; i < len; i++) { + errMsg.append(" "); + errMsg.append(mBindArgs[i]); + } + errMsg.append(" "); + IllegalStateException leakProgram = new IllegalStateException( + errMsg.toString(), e); + throw leakProgram; + } + } + } + + @Override + public void bindNull(int index) { + mBindArgs[index - 1] = null; + if (!mClosed) super.bindNull(index); + } + + @Override + public void bindLong(int index, long value) { + mBindArgs[index - 1] = Long.toString(value); + if (!mClosed) super.bindLong(index, value); + } + + @Override + public void bindDouble(int index, double value) { + mBindArgs[index - 1] = Double.toString(value); + if (!mClosed) super.bindDouble(index, value); + } + + @Override + public void bindString(int index, String value) { + mBindArgs[index - 1] = value; + if (!mClosed) super.bindString(index, value); + } + + public void bindArguments(Object[] args){ + if(args != null && args.length > 0){ + for(int i = 0; i < args.length; i++){ + Object value = args[i]; + if(value == null){ + bindNull(i + 1); + } else if (value instanceof Double) { + bindDouble(i + 1, (Double)value); + } else if (value instanceof Float) { + float number = ((Number)value).floatValue(); + bindDouble(i + 1, Double.valueOf(number)); + } else if (value instanceof Long) { + bindLong(i + 1, (Long)value); + } else if(value instanceof Integer) { + int number = ((Number) value).intValue(); + bindLong(i + 1, Long.valueOf(number)); + } else if (value instanceof Boolean) { + bindLong(i + 1, (Boolean)value ? 1 : 0); + } else if (value instanceof byte[]) { + bindBlob(i + 1, (byte[])value); + } else { + bindString(i + 1, value.toString()); + } + } + } + } + + private final native int native_fill_window(CursorWindow window, + int startPos, int requiredPos, + int offsetParam, int maxRead, + int lastPos); + + private final native int native_column_count(); + + private final native String native_column_name(int columnIndex); +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..d004218b0409f739bd40a0c73d0d1d894fd41d0f --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.*; +import net.sqlcipher.utils.TextUtils; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.data.resultset.ResultSet; + +/** + * This is a convience class that helps build SQL queries to be sent to + * {@link SQLiteDatabase} objects. + */ +public class SQLiteQueryBuilder +{ + private static final String TAG = "SQLiteQueryBuilder"; + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00207, "SQLiteQueryBuilder"); + private static final Pattern sLimitPattern = + Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); + + private Map mProjectionMap = null; + private String mTables = ""; + private StringBuilder mWhereClause = null; // lazily created + private boolean mDistinct; + private SQLiteDatabase.CursorFactory mFactory; + private boolean mStrictProjectionMap; + private static final String _COUNT = "_count"; + + public SQLiteQueryBuilder() { + mDistinct = false; + mFactory = null; + } + + /** + * Mark the query as DISTINCT. + * + * @param distinct if true the query is DISTINCT, otherwise it isn't + */ + public void setDistinct(boolean distinct) { + mDistinct = distinct; + } + + /** + * Returns the list of tables being queried + * + * @return the list of tables being queried + */ + public String getTables() { + return mTables; + } + + /** + * Sets the list of tables to query. Multiple tables can be specified to perform a join. + * For example: + * setTables("foo, bar") + * setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)") + * + * @param inTables the list of tables to query on + */ + public void setTables(String inTables) { + mTables = inTables; + } + + /** + * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded + * by parenthesis and ANDed with the selection passed to {@link #query}. The final + * WHERE clause looks like: + * + * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) + * + * @param inWhere the chunk of text to append to the WHERE clause. + */ + public void appendWhere(CharSequence inWhere) { + if (mWhereClause == null) { + mWhereClause = new StringBuilder(inWhere.length() + 16); + } + if (mWhereClause.length() == 0) { + mWhereClause.append('('); + } + mWhereClause.append(inWhere); + } + + /** + * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded + * by parenthesis and ANDed with the selection passed to {@link #query}. The final + * WHERE clause looks like: + * + * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) + * + * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped + * to avoid SQL injection attacks + */ + public void appendWhereEscapeString(String inWhere) { + if (mWhereClause == null) { + mWhereClause = new StringBuilder(inWhere.length() + 16); + } + if (mWhereClause.length() == 0) { + mWhereClause.append('('); + } + DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); + } + + /** + * Sets the projection map for the query. The projection map maps + * from column names that the caller passes into query to database + * column names. This is useful for renaming columns as well as + * disambiguating column names when doing joins. For example you + * could map "name" to "people.name". If a projection map is set + * it must contain all column names the user may request, even if + * the key and value are the same. + * + * @param columnMap maps from the user column names to the database column names + */ + public void setProjectionMap(Map columnMap) { + mProjectionMap = columnMap; + } + + /** + * Sets the cursor factory to be used for the query. You can use + * one factory for all queries on a database but it is normally + * easier to specify the factory when doing this query. @param + * factory the factor to use + * @param factory CursorFactory + */ + public void setCursorFactory(SQLiteDatabase.CursorFactory factory) { + mFactory = factory; + } + + /** + * setStrictProjectionMap + * @hide hide + * @param flag flag + */ + public void setStrictProjectionMap(boolean flag) { + mStrictProjectionMap = flag; + } + + /** + * Build an SQL query string from the given clauses. + * + * @param distinct true if you want each row to be unique, false otherwise. + * @param tables The table names to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param where A filter declaring which rows to return, formatted as an SQL + * WHERE clause (excluding the WHERE itself). Passing null will + * return all rows for the given URL. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @return the SQL query string + */ + public static String buildQueryString( + boolean distinct, String tables, String[] columns, String where, + String groupBy, String having, String orderBy, String limit) { + if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) { + throw new IllegalArgumentException( + "HAVING clauses are only permitted when using a groupBy clause"); + } + if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { + throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); + } + + StringBuilder query = new StringBuilder(120); + + query.append("SELECT "); + if (distinct) { + query.append("DISTINCT "); + } + if (columns != null && columns.length != 0) { + appendColumns(query, columns); + } else { + query.append("* "); + } + query.append("FROM "); + query.append(tables); + appendClause(query, " WHERE ", where); + appendClause(query, " GROUP BY ", groupBy); + appendClause(query, " HAVING ", having); + appendClause(query, " ORDER BY ", orderBy); + appendClause(query, " LIMIT ", limit); + + return query.toString(); + } + + private static void appendClause(StringBuilder s, String name, String clause) { + if (!TextUtils.isEmpty(clause)) { + s.append(name); + s.append(clause); + } + } + + private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) { + if (!TextUtils.isEmpty(clause)) { + s.append(name); + DatabaseUtils.appendEscapedSQLString(s, clause); + } + } + + /** + * Add the names that are non-null in columns to s, separating + * them with commas. + * @param s string + * @param columns columns + */ + public static void appendColumns(StringBuilder s, String[] columns) { + int n = columns.length; + + for (int i = 0; i < n; i++) { + String column = columns[i]; + + if (column != null) { + if (i > 0) { + s.append(", "); + } + s.append(column); + } + } + s.append(' '); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn A list of which columns to return. Passing + * null will return all columns, which is discouraged to prevent + * reading data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @param groupBy A filter declaring how to group rows, formatted + * as an SQL GROUP BY clause (excluding the GROUP BY + * itself). Passing null will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in + * the cursor, if row grouping is being used, formatted as an + * SQL HAVING clause (excluding the HAVING itself). Passing + * null will cause all row groups to be included, and is + * required when row grouping is not being used. + * @param sortOrder How to order the rows, formatted as an SQL + * ORDER BY clause (excluding the ORDER BY itself). Passing null + * will use the default sort order, which may be unordered. + * @return a cursor over the result set + * + */ + public ResultSet query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder) { + return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, + null /* limit */); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn A list of which columns to return. Passing + * null will return all columns, which is discouraged to prevent + * reading data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @param groupBy A filter declaring how to group rows, formatted + * as an SQL GROUP BY clause (excluding the GROUP BY + * itself). Passing null will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in + * the cursor, if row grouping is being used, formatted as an + * SQL HAVING clause (excluding the HAVING itself). Passing + * null will cause all row groups to be included, and is + * required when row grouping is not being used. + * @param sortOrder How to order the rows, formatted as an SQL + * ORDER BY clause (excluding the ORDER BY itself). Passing null + * will use the default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @return a cursor over the result set + * + */ + public ResultSet query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit) { + if (mTables == null) { + return null; + } + + String sql = buildQuery( + projectionIn, selection, selectionArgs, groupBy, having, + sortOrder, limit); + + if (HiLog.isLoggable(0x00208, TAG, HiLog.DEBUG)) { + HiLog.debug(label, "Performing query: " + sql); + } + return db.rawQueryWithFactory( + mFactory, sql, selectionArgs, + SQLiteDatabase.findEditTable(mTables)); + } + + /** + * Construct a SELECT statement suitable for use in a group of + * SELECT statements that will be joined through UNION operators + * in buildUnionQuery. + * + * @param projectionIn A list of which columns to return. Passing + * null will return all columns, which is discouraged to + * prevent reading data from storage that isn't going to be + * used. + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given + * URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @param groupBy A filter declaring how to group rows, formatted + * as an SQL GROUP BY clause (excluding the GROUP BY itself). + * Passing null will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in + * the cursor, if row grouping is being used, formatted as an + * SQL HAVING clause (excluding the HAVING itself). Passing + * null will cause all row groups to be included, and is + * required when row grouping is not being used. + * @param sortOrder How to order the rows, formatted as an SQL + * ORDER BY clause (excluding the ORDER BY itself). Passing null + * will use the default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @return the resulting SQL SELECT statement + */ + public String buildQuery( + String[] projectionIn, String selection, String[] selectionArgs, + String groupBy, String having, String sortOrder, String limit) { + String[] projection = computeProjection(projectionIn); + + StringBuilder where = new StringBuilder(); + boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; + + if (hasBaseWhereClause) { + where.append(mWhereClause.toString()); + where.append(')'); + } + + // Tack on the user's selection, if present. + if (selection != null && selection.length() > 0) { + if (hasBaseWhereClause) { + where.append(" AND "); + } + + where.append('('); + where.append(selection); + where.append(')'); + } + + return buildQueryString( + mDistinct, mTables, projection, where.toString(), + groupBy, having, sortOrder, limit); + } + + /** + * Construct a SELECT statement suitable for use in a group of + * SELECT statements that will be joined through UNION operators + * in buildUnionQuery. + * + * @param typeDiscriminatorColumn the name of the result column + * whose cells will contain the name of the table from which + * each row was drawn. + * @param unionColumns the names of the columns to appear in the + * result. This may include columns that do not appear in the + * table this SELECT is querying (i.e. mTables), but that do + * appear in one of the other tables in the UNION query that we + * are constructing. + * @param columnsPresentInTable a Set of the names of the columns + * that appear in this table (i.e. in the table whose name is + * mTables). Since columns in unionColumns include columns that + * appear only in other tables, we use this array to distinguish + * which ones actually are present. Other columns will have + * NULL values for results from this subquery. + * @param computedColumnsOffset all columns in unionColumns before + * this index are included under the assumption that they're + * computed and therefore won't appear in columnsPresentInTable, + * e.g. "date * 1000 as normalized_date" + * @param typeDiscriminatorValue the value used for the + * type-discriminator column in this subquery + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given + * URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @param groupBy A filter declaring how to group rows, formatted + * as an SQL GROUP BY clause (excluding the GROUP BY itself). + * Passing null will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in + * the cursor, if row grouping is being used, formatted as an + * SQL HAVING clause (excluding the HAVING itself). Passing + * null will cause all row groups to be included, and is + * required when row grouping is not being used. + * @return the resulting SQL SELECT statement + */ + public String buildUnionSubQuery( + String typeDiscriminatorColumn, + String[] unionColumns, + Set columnsPresentInTable, + int computedColumnsOffset, + String typeDiscriminatorValue, + String selection, + String[] selectionArgs, + String groupBy, + String having) { + int unionColumnsCount = unionColumns.length; + String[] projectionIn = new String[unionColumnsCount]; + + for (int i = 0; i < unionColumnsCount; i++) { + String unionColumn = unionColumns[i]; + + if (unionColumn.equals(typeDiscriminatorColumn)) { + projectionIn[i] = "'" + typeDiscriminatorValue + "' AS " + + typeDiscriminatorColumn; + } else if (i <= computedColumnsOffset + || columnsPresentInTable.contains(unionColumn)) { + projectionIn[i] = unionColumn; + } else { + projectionIn[i] = "NULL AS " + unionColumn; + } + } + return buildQuery( + projectionIn, selection, selectionArgs, groupBy, having, + null /* sortOrder */, + null /* limit */); + } + + /** + * Given a set of subqueries, all of which are SELECT statements, + * construct a query that returns the union of what those + * subqueries return. + * @param subQueries an array of SQL SELECT statements, all of + * which must have the same columns as the same positions in + * their results + * @param sortOrder How to order the rows, formatted as an SQL + * ORDER BY clause (excluding the ORDER BY itself). Passing + * null will use the default sort order, which may be unordered. + * @param limit The limit clause, which applies to the entire union result set + * + * @return the resulting SQL SELECT statement + */ + public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) { + StringBuilder query = new StringBuilder(128); + int subQueryCount = subQueries.length; + String unionOperator = mDistinct ? " UNION " : " UNION ALL "; + + for (int i = 0; i < subQueryCount; i++) { + if (i > 0) { + query.append(unionOperator); + } + query.append(subQueries[i]); + } + appendClause(query, " ORDER BY ", sortOrder); + appendClause(query, " LIMIT ", limit); + return query.toString(); + } + + private String[] computeProjection(String[] projectionIn) { + if (projectionIn != null && projectionIn.length > 0) { + if (mProjectionMap != null) { + String[] projection = new String[projectionIn.length]; + int length = projectionIn.length; + + for (int i = 0; i < length; i++) { + String userColumn = projectionIn[i]; + String column = mProjectionMap.get(userColumn); + + if (column != null) { + projection[i] = column; + continue; + } + + if (!mStrictProjectionMap && + ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { + /* A column alias already exist */ + projection[i] = userColumn; + continue; + } + + throw new IllegalArgumentException("Invalid column " + + projectionIn[i]); + } + return projection; + } else { + return projectionIn; + } + } else if (mProjectionMap != null) { + // Return all columns in projection map. + Set> entrySet = mProjectionMap.entrySet(); + String[] projection = new String[entrySet.size()]; + Iterator> entryIter = entrySet.iterator(); + int i = 0; + + while (entryIter.hasNext()) { + Entry entry = entryIter.next(); + + // Don't include the _count column when people ask for no projection. + if (entry.getKey().equals(_COUNT)) { + continue; + } + projection[i++] = entry.getValue(); + } + return projection; + } + return null; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java new file mode 100644 index 0000000000000000000000000000000000000000..4b36c05f001759ab53aa991a9f4555a7dfe18151 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java @@ -0,0 +1,20 @@ +package net.sqlcipher.database; + +public class SQLiteQueryStats { + long totalQueryResultSize = 0L; + long largestIndividualRowSize = 0L; + + public SQLiteQueryStats(long totalQueryResultSize, + long largestIndividualRowSize) { + this.totalQueryResultSize = totalQueryResultSize; + this.largestIndividualRowSize = largestIndividualRowSize; + } + + public long getTotalQueryResultSize(){ + return totalQueryResultSize; + } + + public long getLargestIndividualRowSize(){ + return largestIndividualRowSize; + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..d2bc6442f0d309a3572ec9eca0e344d2a7f71893 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import ohos.miscservices.timeutility.Time; +import net.sqlcipher.harmonyx.SupportSQLiteStatement; + +/** + * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. + * The statement cannot return multiple rows, but 1x1 result sets are allowed. + * Don't use SQLiteStatement constructor directly, please use + * {@link SQLiteDatabase#compileStatement(String)} + * + * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple + * threads should perform its own synchronization when using the SQLiteStatement. + */ +public class SQLiteStatement extends SQLiteProgram implements + SupportSQLiteStatement +{ + /** + * Don't use SQLiteStatement constructor directly, please use + * {@link SQLiteDatabase#compileStatement(String)} + * @param db + * @param sql + */ + /* package */ SQLiteStatement(SQLiteDatabase db, String sql) { + super(db, sql); + } + + /** + * Execute this SQL statement, if it is not a query. For example, + * CREATE TABLE, DELTE, INSERT, etc. + * + * @throws net.sqlcipher.SQLException If the SQL string is invalid for + * some reason + */ + @Override + public void execute() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + + acquireReference(); + try { + native_execute(); + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + /** + * Execute this SQL statement and return the ID of the row inserted due to this call. + * The SQL statement should be an INSERT for this to be a useful call. + * + * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. + * + * @throws net.sqlcipher.SQLException If the SQL string is invalid for + * some reason + */ + @Override + public long executeInsert() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + + acquireReference(); + try { + native_execute(); + return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1; + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + @Override + public int executeUpdateDelete() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + + acquireReference(); + try { + native_execute(); + return mDatabase.lastChangeCount(); + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + /** + * Execute a statement that returns a 1 by 1 table with a numeric value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws net.sqlcipher.sqlite.SQLiteDoneException if the query returns zero rows + */ + @Override + public long simpleQueryForLong() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + + acquireReference(); + try { + long retValue = native_1x1_long(); + return retValue; + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + /** + * Execute a statement that returns a 1 by 1 table with a text value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws net.sqlcipher.sqlite.SQLiteDoneException if the query returns zero rows + */ + @Override + public String simpleQueryForString() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = Time.getRealActiveTime(); + mDatabase.lock(); + + acquireReference(); + try { + String retValue = native_1x1_string(); + return retValue; + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + + private final native void native_execute(); + private final native long native_1x1_long(); + private final native String native_1x1_string(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java new file mode 100644 index 0000000000000000000000000000000000000000..69680ee308ae66e54cd02ed5bf80c7905b8dcc53 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +/** + * A listener for transaction events. + */ +public interface SQLiteTransactionListener { + /** + * Called immediately after the transaction begins. + */ + void onBegin(); + + /** + * Called immediately before commiting the transaction. + */ + void onCommit(); + + /** + * Called if the transaction is about to be rolled back. + */ + void onRollback(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java b/sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..8e74a26b03da2657ff7c2077a5cef457d0859846 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.data.dataability.DataAbilityPredicates; +import ohos.data.rdb.ValuesBucket; +import ohos.app.Context; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import ohos.data.resultset.ResultSet; +import ohos.utils.net.Uri; +import ohos.agp.window.dialog.ToastDialog; + +/** + * @hide + */ + +public final class SqliteWrapper { + private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00206, "SqliteWrapper"); + private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE + = "unable to open database file"; + + private SqliteWrapper() { + // Forbidden being instantiated. + } + + // FIXME: need to optimize this method. + private static boolean isLowMemory(SQLiteException e) { + return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE); + } + + public static void checkSQLiteException(Context context, SQLiteException e) { + if (isLowMemory(e)) { + ToastDialog toast = new ToastDialog(context); + toast.setText(e.getMessage()).show(); + } else { + throw e; + } + } + + public static ResultSet query(Context context, DataAbilityHelper resolver, Uri uri, + String[] projection, String selection, String[] selectionArgs, String sortOrder) { + try { + DataAbilityPredicates predicates = createPredicateFromSelection(selection, selectionArgs); + predicates.setOrder(sortOrder); + return (ResultSet) resolver.query(uri, projection, predicates); + } catch (SQLiteException e) { + HiLog.error(label, "Catch a SQLiteException when query: ", e); + checkSQLiteException(context, e); + return null; + } catch (DataAbilityRemoteException e) { + HiLog.error(label, "Catch a SQLiteException when query: ", e); + return null; + } + } + + private static DataAbilityPredicates createPredicateFromSelection(String selection, String[] selectionArgs) { + DataAbilityPredicates predicates = new DataAbilityPredicates(selection); + List args = new ArrayList<>(Arrays.asList(selectionArgs)); + predicates.setWhereArgs(args); + return predicates; + } + +/* public static boolean requery(Context context, ResultSet cursor) { //deprecated method, removed + try { + return cursor.requery(); + } catch (SQLiteException e) { + HiLog.error(label, "Catch a SQLiteException when requery: ", e); + checkSQLiteException(context, e); + return false; + } + }*/ + + public static int update(Context context, DataAbilityHelper resolver, Uri uri, + ValuesBucket values, String where, String[] selectionArgs) { + try { + DataAbilityPredicates predicates = createPredicateFromSelection(where,selectionArgs); + return resolver.update(uri, values, predicates); + } catch (SQLiteException e) { + HiLog.error(label, "Catch a SQLiteException when update: ", e); + checkSQLiteException(context, e); + return -1; + } catch (DataAbilityRemoteException e) { + HiLog.error(label, "Catch a SQLiteException when query: ", e); + return -1; + } + } + + public static int delete(Context context, DataAbilityHelper resolver, Uri uri, + String where, String[] selectionArgs) { + try { + DataAbilityPredicates predicates = createPredicateFromSelection(where,selectionArgs); + return resolver.delete(uri, predicates); + } catch (SQLiteException e) { + HiLog.error(label, "Catch a SQLiteException when delete: ", e); + checkSQLiteException(context, e); + return -1; + }catch (DataAbilityRemoteException e) { + HiLog.error(label, "Catch a SQLiteException when query: ", e); + return -1; + } + } + + public static int insert(Context context, DataAbilityHelper resolver, + Uri uri, ValuesBucket values) { + try { + return resolver.insert(uri, values); + } catch (DataAbilityRemoteException d) { + HiLog.error(label, "Catch a DataAbilityRemoteException when insert: ", d); + return -1; + } catch (SQLiteException e) { + HiLog.error(label, "Catch a SQLiteException when insert: ", e); + checkSQLiteException(context, e); + return -1; + } + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java b/sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..91d672539f7ab4a4e78aeea0e5d241aa46f45a7d --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Mark L. Murphy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.harmonyx.SupportSQLiteOpenHelper; + +public class SupportFactory implements SupportSQLiteOpenHelper.Factory { + private final byte[] passphrase; + private final SQLiteDatabaseHook hook; + private final boolean clearPassphrase; + + public SupportFactory(byte[] passphrase) { + this(passphrase, (SQLiteDatabaseHook)null); + } + + public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook) { + this(passphrase, hook, true); + } + + public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook, + boolean clearPassphrase) { + this.passphrase = passphrase; + this.hook = hook; + this.clearPassphrase = clearPassphrase; + } + + @Override + public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) { + return new SupportHelper(configuration, passphrase, hook, clearPassphrase); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java b/sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..230a7c65060dafeaa988be5362ca259ebfb91094 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java @@ -0,0 +1,117 @@ + /* + * Copyright (C) 2019 Mark L. Murphy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.harmonyx.SupportSQLiteDatabase; +import net.sqlcipher.harmonyx.SupportSQLiteOpenHelper; + + public class SupportHelper implements SupportSQLiteOpenHelper { + private SQLiteOpenHelper standardHelper; + private byte[] passphrase; + private final boolean clearPassphrase; + + SupportHelper(final SupportSQLiteOpenHelper.Configuration configuration, + byte[] passphrase, final SQLiteDatabaseHook hook, + boolean clearPassphrase) { + SQLiteDatabase.loadLibs(configuration.context); + this.passphrase = passphrase; + this.clearPassphrase = clearPassphrase; + + standardHelper = + new SQLiteOpenHelper(configuration.context, configuration.name, + null, configuration.callback.version, hook) { + @Override + public void onCreate(SQLiteDatabase db) { + configuration.callback.onCreate(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, + int newVersion) { + configuration.callback.onUpgrade(db, oldVersion, + newVersion); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, + int newVersion) { + configuration.callback.onDowngrade(db, oldVersion, + newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + configuration.callback.onOpen(db); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + configuration.callback.onConfigure(db); + } + }; + } + + @Override + public String getDatabaseName() { + return standardHelper.getDatabaseName(); + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + standardHelper.setWriteAheadLoggingEnabled(enabled); + } + + @Override + public SupportSQLiteDatabase getWritableDatabase() { + SQLiteDatabase result; + try { + result = standardHelper.getWritableDatabase(passphrase); + } catch (SQLiteException ex){ + if(passphrase != null){ + boolean isCleared = true; + for(byte b : passphrase){ + isCleared = isCleared && (b == (byte)0); + } + if (isCleared) { + throw new IllegalStateException("The passphrase appears to be cleared. This happens by " + + "default the first time you use the factory to open a database, so we can remove the " + + "cleartext passphrase from memory. If you close the database yourself, please use a " + + "fresh SupportFactory to reopen it. If something else (e.g., Room) closed the " + + "database, and you cannot control that, use SupportFactory boolean constructor option " + + "to opt out of the automatic password clearing step. See the project README for more information.", ex); + } + } + throw ex; + } + if(clearPassphrase && passphrase != null) { + for (int i = 0; i < passphrase.length; i++) { + passphrase[i] = (byte)0; + } + } + return result; + } + + @Override + public SupportSQLiteDatabase getReadableDatabase() { + return getWritableDatabase(); + } + + @Override + public void close() { + standardHelper.close(); + } + } diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SimpleSQLiteQuery.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SimpleSQLiteQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..ad7ae83b01255a495b55095ee4f2bae4c4088e54 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SimpleSQLiteQuery.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +/** + * A basic implementation of {@link SupportSQLiteQuery} which receives a query and its args and + * binds args based on the passed in Object type. + */ +public final class SimpleSQLiteQuery implements SupportSQLiteQuery { + private final String mQuery; + private final Object[] mBindArgs; + + /** + * Creates an SQL query with the sql string and the bind arguments. + * + * @param query The query string, can include bind arguments (.e.g ?). + * @param bindArgs The bind argument value that will replace the placeholders in the query. + */ + public SimpleSQLiteQuery(String query, Object[] bindArgs) { + mQuery = query; + mBindArgs = bindArgs; + } + + /** + * Creates an SQL query without any bind arguments. + * + * @param query The SQL query to execute. Cannot include bind parameters. + */ + public SimpleSQLiteQuery(String query) { + this(query, null); + } + + @Override + public String getSql() { + return mQuery; + } + + @Override + public void bindTo(SupportSQLiteProgram statement) { + bind(statement, mBindArgs); + } + + @Override + public int getArgCount() { + return mBindArgs == null ? 0 : mBindArgs.length; + } + + /** + * Binds the given arguments into the given sqlite statement. + * + * @param statement The sqlite statement + * @param bindArgs The list of bind arguments + */ + public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) { + if (bindArgs == null) { + return; + } + final int limit = bindArgs.length; + for (int i = 0; i < limit; i++) { + final Object arg = bindArgs[i]; + bind(statement, i + 1, arg); + } + } + + private static void bind(SupportSQLiteProgram statement, int index, Object arg) { + if (arg == null) { + statement.bindNull(index); + } else if (arg instanceof byte[]) { + statement.bindBlob(index, (byte[]) arg); + } else if (arg instanceof Float) { + statement.bindDouble(index, (Float) arg); + } else if (arg instanceof Double) { + statement.bindDouble(index, (Double) arg); + } else if (arg instanceof Long) { + statement.bindLong(index, (Long) arg); + } else if (arg instanceof Integer) { + statement.bindLong(index, (Integer) arg); + } else if (arg instanceof Short) { + statement.bindLong(index, (Short) arg); + } else if (arg instanceof Byte) { + statement.bindLong(index, (Byte) arg); + } else if (arg instanceof String) { + statement.bindString(index, (String) arg); + } else if (arg instanceof Boolean) { + statement.bindLong(index, ((Boolean) arg) ? 1 : 0); + } else { + throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index + + " Supported types: null, byte[], float, double, long, int, short, byte," + + " string"); + } + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteDatabase.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..54b4abda75702e05f11ef4e68f48215bef585693 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteDatabase.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +import net.sqlcipher.SQLException; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.TransactionObserver; + +import ohos.data.rdb.ValuesBucket; +import ohos.data.resultset.ResultSet; +import ohos.utils.Pair; + +import java.io.Closeable; +import java.util.List; +import java.util.Locale; + + +@SuppressWarnings("unused") +public interface SupportSQLiteDatabase extends Closeable { + /** + * Compiles the given SQL statement. + * + * @param sql The sql query. + * @return Compiled statement. + */ + SupportSQLiteStatement compileStatement(String sql); + + /** + * Begins a transaction in EXCLUSIVE mode. + *

+ * Transactions can be nested. + * When the outer transaction is ended all of + * the work done in that transaction and all of the nested transactions will be committed or + * rolled back. The changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. + *

+ *

Here is the standard idiom for transactions: + * + *

+     *   db.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + void beginTransaction(); + + /** + * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When + * the outer transaction is ended all of the work done in that transaction + * and all of the nested transactions will be committed or rolled back. The + * changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they + * will be committed. + *

+ * Here is the standard idiom for transactions: + * + *

+     *   db.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + void beginTransactionNonExclusive(); + + /** + * Begins a transaction in EXCLUSIVE mode. + *

+ * Transactions can be nested. + * When the outer transaction is ended all of + * the work done in that transaction and all of the nested transactions will be committed or + * rolled back. The changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. + *

+ *

Here is the standard idiom for transactions: + * + *

+     *   db.beginTransactionWithListener(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ * + * @param transactionListener listener that should be notified when the transaction begins, + * commits, or is rolled back, either explicitly or by a call to + * {@link #yieldIfContendedSafely}. + */ + void beginTransactionWithListener(TransactionObserver transactionListener); + + /** + * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When + * the outer transaction is ended all of the work done in that transaction + * and all of the nested transactions will be committed or rolled back. The + * changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they + * will be committed. + *

+ * Here is the standard idiom for transactions: + * + *

+     *   db.beginTransactionWithListenerNonExclusive(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ * + * @param transactionListener listener that should be notified when the + * transaction begins, commits, or is rolled back, either + * explicitly or by a call to {@link #yieldIfContendedSafely}. + */ + void beginTransactionWithListenerNonExclusive(TransactionObserver transactionListener); + + /** + * End a transaction. See beginTransaction for notes about how to use this and when transactions + * are committed and rolled back. + */ + void endTransaction(); + + /** + * Marks the current transaction as successful. Do not do any more database work between + * calling this and calling endTransaction. Do as little non-database work as possible in that + * situation too. If any errors are encountered between this and endTransaction the transaction + * will still be committed. + * + * @throws IllegalStateException if the current thread is not in a transaction or the + * transaction is already marked as successful. + */ + void setTransactionSuccessful(); + + /** + * Returns true if the current thread has a transaction pending. + * + * @return True if the current thread is in a transaction. + */ + boolean inTransaction(); + + /** + * Returns true if the current thread is holding an active connection to the database. + *

+ * The name of this method comes from a time when having an active connection + * to the database meant that the thread was holding an actual lock on the + * database. Nowadays, there is no longer a true "database lock" although threads + * may block if they cannot acquire a database connection to perform a + * particular operation. + *

+ * + * @return True if the current thread is holding an active connection to the database. + */ + boolean isDbLockedByCurrentThread(); + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @return true if the transaction was yielded + */ + boolean yieldIfContendedSafely(); + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if + * the lock was actually yielded. This will allow other background + * threads to make some + * more progress than they would if we started the transaction + * immediately. + * @return true if the transaction was yielded + */ + boolean yieldIfContendedSafely(long sleepAfterYieldDelay); + + /** + * Gets the database version. + * + * @return the database version + */ + int getVersion(); + + /** + * Sets the database version. + * + * @param version the new database version + */ + void setVersion(int version); + + /** + * Returns the maximum size the database may grow to. + * + * @return the new maximum database size + */ + long getMaximumSize(); + + /** + * Sets the maximum size the database will grow to. The maximum size cannot + * be set below the current size. + * + * @param numBytes the maximum database size, in bytes + * @return the new maximum database size + */ + long setMaximumSize(long numBytes); + + /** + * Returns the current database page size, in bytes. + * + * @return the database page size, in bytes + */ + long getPageSize(); + + /** + * Sets the database page size. The page size must be a power of two. This + * method does not work if any data has been written to the database file, + * and must be called right after the database has been created. + * + * @param numBytes the database page size, in bytes + */ + void setPageSize(long numBytes); + + /** + * Runs the given query on the database. If you would like to have typed bind arguments, + * use {@link #query(SupportSQLiteQuery)}. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * @see #query(SupportSQLiteQuery) + */ + ResultSet query(String query); + + /** + * Runs the given query on the database. If you would like to have bind arguments, + * use {@link #query(SupportSQLiteQuery)}. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @param bindArgs The query arguments to bind. + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * @see #query(SupportSQLiteQuery) + */ + ResultSet query(String query, Object[] bindArgs); + + /** + * Runs the given query on the database. + *

+ * This class allows using type safe sql program bindings while running queries. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + * @see SimpleSQLiteQuery + */ + ResultSet query(SupportSQLiteQuery query); + + /** + * Runs the given query on the database. + *

+ * This class allows using type safe sql program bindings while running queries. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return A {@link ResultSet} object, which is positioned before the first entry. Note that + * {@link ResultSet}s are not synchronized, see the documentation for more details. + */ + /* @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + ResultSet query(SupportSQLiteQuery query*//*, CancellationSignal cancellationSignal*//*);*/ + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param values this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @param conflictAlgorithm for insert conflict resolver. One of + * {@link RdbStore.ConflictResolution#ON_CONFLICT_NONE}, {@link RdbStore.ConflictResolution#ON_CONFLICT_ROLLBACK}, + * {@link RdbStore.ConflictResolution#ON_CONFLICT_ABORT}, {@link RdbStore.ConflictResolution#ON_CONFLICT_FAIL}, + * {@link RdbStore.ConflictResolution#ON_CONFLICT_IGNORE}, {@link RdbStore.ConflictResolution#ON_CONFLICT_REPLACE}. + * @return the row ID of the newly inserted row, or -1 if an error occurred + * @throws SQLException If the insert fails + */ + long insert(String table, int conflictAlgorithm, ValuesBucket values) throws SQLException; + + /** + * Convenience method for deleting rows in the database. + * + * @param table the table to delete from + * @param whereClause the optional WHERE clause to apply when deleting. + * Passing null will delete all rows. + * @param whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @return the number of rows affected if a whereClause is passed in, 0 + * otherwise. To remove all rows and get a count pass "1" as the + * whereClause. + */ + int delete(String table, String whereClause, Object[] whereArgs); + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param conflictAlgorithm for update conflict resolver. One of + * {@link RdbStore.ConflictResolution#ON_CONFLICT_NONE}, {@link RdbStore.ConflictResolution#ON_CONFLICT_ROLLBACK}, + * {@link RdbStore.ConflictResolution#ON_CONFLICT_ABORT}, {@link RdbStore.ConflictResolution#ON_CONFLICT_FAIL}, + * {@link RdbStore.ConflictResolution#ON_CONFLICT_IGNORE}, {@link RdbStore.ConflictResolution#ON_CONFLICT_REPLACE}. + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * @param whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @return the number of rows affected + */ + int update(String table, int conflictAlgorithm, + ValuesBucket values, String whereClause, Object[] whereArgs); + + /** + * Execute a single SQL statement that does not return any data. + *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are + * not supported. + * @throws SQLException if the SQL string is invalid + * @see #query(SupportSQLiteQuery) + */ + void execSQL(String sql) throws SQLException; + + /** + * Execute a single SQL statement that does not return any data. + *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons + * are + * not supported. + * @param bindArgs only byte[], String, Long and Double are supported in selectionArgs. + * @throws SQLException if the SQL string is invalid + * @see #query(SupportSQLiteQuery) + */ + void execSQL(String sql, Object[] bindArgs) throws SQLException; + + /** + * Returns true if the database is opened as read only. + * + * @return True if database is opened as read only. + */ + boolean isReadOnly(); + + /** + * Returns true if the database is currently open. + * + * @return True if the database is currently open (has not been closed). + */ + boolean isOpen(); + + /** + * Returns true if the new version code is greater than the current database version. + * + * @param newVersion The new version code. + * @return True if the new version code is greater than the current database version. + */ + boolean needUpgrade(int newVersion); + + /** + * Gets the path to the database file. + * + * @return The path to the database file. + */ + String getPath(); + + /** + * Sets the locale for this database. Does nothing if this database has + * the {@link SQLiteDatabase#NO_LOCALIZED_COLLATORS} flag set or was opened read only. + * + * @param locale The new locale. + * @throws SQLException if the locale could not be set. The most common reason + * for this is that there is no collator available for the locale you + * requested. + * In this case the database remains unchanged. + */ + void setLocale(Locale locale); + + /** + * Sets the maximum size of the prepared-statement cache for this database. + * (size of the cache = number of compiled-sql-statements stored in the cache). + *

+ * Maximum cache size can ONLY be increased from its current size (default = 10). + * If this method is called with smaller size than the current maximum value, + * then IllegalStateException is thrown. + *

+ * This method is thread-safe. + * + * @param cacheSize the size of the cache. can be (0 to + * {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}) + * @throws IllegalStateException if input cacheSize gt; + * {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}. + */ + void setMaxSqlCacheSize(int cacheSize); + + /** + * Sets whether foreign key constraints are enabled for the database. + *

+ * By default, foreign key constraints are not enforced by the database. + * This method allows an application to enable foreign key constraints. + * It must be called each time the database is opened to ensure that foreign + * key constraints are enabled for the session. + *

+ * A good time to call this method is right after calling {@code #openOrCreateDatabase} + * or in the {@link SupportSQLiteOpenHelper.Callback#onConfigure} callback. + *

+ * When foreign key constraints are disabled, the database does not check whether + * changes to the database will violate foreign key constraints. Likewise, when + * foreign key constraints are disabled, the database will not execute cascade + * delete or update triggers. As a result, it is possible for the database + * state to become inconsistent. To perform a database integrity check, + * call {@link #isDatabaseIntegrityOk}. + *

+ * This method must not be called while a transaction is in progress. + *

+ * See also SQLite Foreign Key Constraints + * for more details about foreign key constraint support. + *

+ * + * @param enable True to enable foreign key constraints, false to disable them. + * @throws IllegalStateException if the are transactions is in progress + * when this method is called. + */ + // @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + void setForeignKeyConstraintsEnabled(boolean enable); + + /** + * This method enables parallel execution of queries from multiple threads on the + * same database. It does this by opening multiple connections to the database + * and using a different database connection for each query. The database + * journal mode is also changed to enable writes to proceed concurrently with reads. + *

+ * When write-ahead logging is not enabled (the default), it is not possible for + * reads and writes to occur on the database at the same time. Before modifying the + * database, the writer implicitly acquires an exclusive lock on the database which + * prevents readers from accessing the database until the write is completed. + *

+ * In contrast, when write-ahead logging is enabled (by calling this method), write + * operations occur in a separate log file which allows reads to proceed concurrently. + * While a write is in progress, readers on other threads will perceive the state + * of the database as it was before the write began. When the write completes, readers + * on other threads will then perceive the new state of the database. + *

+ * It is a good idea to enable write-ahead logging whenever a database will be + * concurrently accessed and modified by multiple threads at the same time. + * However, write-ahead logging uses significantly more memory than ordinary + * journaling because there are multiple connections to the same database. + * So if a database will only be used by a single thread, or if optimizing + * concurrency is not very important, then write-ahead logging should be disabled. + *

+ * After calling this method, execution of queries in parallel is enabled as long as + * the database remains open. To disable execution of queries in parallel, either + * call {@link #disableWriteAheadLogging} or close the database and reopen it. + *

+ * The maximum number of connections used to execute queries in parallel is + * dependent upon the device memory and possibly other properties. + *

+ * If a query is part of a transaction, then it is executed on the same database handle the + * transaction was begun. + *

+ * Writers should use {@link #beginTransactionNonExclusive()} or + * {@link #beginTransactionWithListenerNonExclusive(TransactionObserver)} + * to start a transaction. Non-exclusive mode allows database file to be in readable + * by other threads executing queries. + *

+ * If the database has any attached databases, then execution of queries in parallel is NOT + * possible. Likewise, write-ahead logging is not supported for read-only databases + * or memory databases. In such cases, {@code enableWriteAheadLogging} returns false. + *

+ * The best way to enable write-ahead logging is to pass the + * {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag to + * {@link SQLiteDatabase#openDatabase}. This is more efficient than calling + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * Another way to enable write-ahead logging is to call {@code enableWriteAheadLogging} + * after opening the database. + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * See also SQLite Write-Ahead Logging for + * more details about how write-ahead logging works. + *

+ * + * @return True if write-ahead logging is enabled. + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when + * there are no + * transactions in progress. + * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING + * @see #disableWriteAheadLogging + */ + boolean enableWriteAheadLogging(); + + /** + * This method disables the features enabled by {@link #enableWriteAheadLogging()}. + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when + * there are no + * transactions in progress. + * @see #enableWriteAheadLogging + */ + //@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + void disableWriteAheadLogging(); + + /** + * Returns true if write-ahead logging has been enabled for this database. + * + * @return True if write-ahead logging has been enabled for this database. + * @see #enableWriteAheadLogging + * @see RdbStore.ConflictResolution#ENABLE_WRITE_AHEAD_LOGGING + */ + //@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + boolean isWriteAheadLoggingEnabled(); + + /** + * Returns list of full path names of all attached databases including the main database + * by executing 'pragma database_list' on the database. + * + * @return ArrayList of pairs of (database name, database file path) or null if the database + * is not open. + */ + List> getAttachedDbs(); + + /** + * Runs 'pragma integrity_check' on the given database (and all the attached databases) + * and returns true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + *

+ * If the result is false, then this method logs the errors reported by the integrity_check + * command execution. + *

+ * Note that 'pragma integrity_check' on a database can take a long time. + * + * @return true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + */ + boolean isDatabaseIntegrityOk(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteOpenHelper.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteOpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..ab949db9b6beaecb18966bbadeeed4967bd53359 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteOpenHelper.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +import ohos.data.DatabaseHelper; +import net.sqlcipher.database.SQLiteException; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.utils.Pair; +import ohos.app.Context; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * An interface to map the behavior of {@link net.sqlcipher.sqlite.SQLiteOpenHelper}. + * Note that since that class requires overriding certain methods, support implementation + * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement + * the methods that should be overridden. + */ +@SuppressWarnings("unused") +public interface SupportSQLiteOpenHelper { + /** + * Return the name of the SQLite database being opened, as given to + * the constructor. + * @return string + */ + String getDatabaseName(); + static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00202, "SupportSQLiteOpenHelper"); + + /** + * Enables or disables the use of write-ahead logging for the database. + * + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + * @param enabled True if write-ahead logging should be enabled, false if it + * should be disabled. + * @see SupportSQLiteDatabase#enableWriteAheadLogging() + */ + /* @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)*/ + void setWriteAheadLoggingEnabled(boolean enabled); + + /** + * Create and/or open a database that will be used for reading and writing. + * The first time this is called, the database will be opened and + * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be + * called. + * + *

Once opened successfully, the database is cached, so you can + * call this method every time you need to write to the database. + * (Make sure to call {@link #close} when you no longer need the database.) + * Errors such as bad permissions or a full disk may cause this method + * to fail, but future attempts may succeed if the problem is fixed.

+ * + *

Database upgrade may take a long time, you + * should not call this method from the application main thread, including + * from {@link ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @return a read/write database object valid until {@link #close} is called + * @throws SQLiteException if the database cannot be opened for writing + */ + SupportSQLiteDatabase getWritableDatabase(); + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + *

Like {@link #getWritableDatabase}, this method may + * take a long time to return, so you should not call it from the + * application main thread, including from + * {@link ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @return a database object valid until {@link #getWritableDatabase} + * or {@link #close} is called. + * @throws SQLiteException if the database cannot be opened + */ + SupportSQLiteDatabase getReadableDatabase(); + + /** + * Close any open database object. + */ + void close(); + + /** + * Handles various lifecycle events for the SQLite connection, similar to + * {@link net.sqlcipher.sqlite.SQLiteOpenHelper}. + */ + @SuppressWarnings({"unused", "WeakerAccess"}) + abstract class Callback { + private static final String TAG = "SupportSQLite"; + /** + * Version number of the database (starting at 1); if the database is older, + * {@link Callback#onUpgrade(SupportSQLiteDatabase, int, int)} + * will be used to upgrade the database; if the database is newer, + * {@link Callback#onDowngrade(SupportSQLiteDatabase, int, int)} + * will be used to downgrade the database. + */ + public final int version; + + /** + * Creates a new Callback to get database lifecycle events. + * @param version The version for the database instance. See {@link #version}. + */ + public Callback(int version) { + this.version = version; + } + + /** + * Called when the database connection is being configured, to enable features such as + * write-ahead logging or foreign key support. + *

+ * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade}, + * or {@link #onOpen} are called. It should not modify the database except to configure the + * database connection as required. + *

+ *

+ * This method should only call methods that configure the parameters of the database + * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging} + * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled}, + * {@link SupportSQLiteDatabase#setLocale}, + * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements. + *

+ * + * @param db The database. + */ + public void onConfigure(SupportSQLiteDatabase db) { + + } + + /** + * Called when the database is created for the first time. This is where the + * creation of tables and the initial population of the tables should happen. + * + * @param db The database. + */ + public abstract void onCreate(SupportSQLiteDatabase db); + + /** + * Called when the database needs to be upgraded. The implementation + * should use this method to drop tables, add tables, or do anything else it + * needs to upgrade to the new schema version. + * + *

+ * The SQLite ALTER TABLE documentation can be found + * here. If you add new columns + * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns + * you can use ALTER TABLE to rename the old table, then create the new table and then + * populate the new table with the contents of the old table. + *

+ * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + *

+ * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion); + + /** + * Called when the database needs to be downgraded. This is strictly similar to + * {@link #onUpgrade} method, but is called whenever current version is newer than requested + * one. + * However, this method is not abstract, so it is not mandatory for a customer to + * implement it. If not overridden, default implementation will reject downgrade and + * throws SQLiteException + * + *

+ * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + *

+ * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + throw new SQLiteException("Can't downgrade database from version " + + oldVersion + " to " + newVersion); + } + + /** + * Called when the database has been opened. The implementation + * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the + * database. + *

+ * This method is called after the database connection has been configured + * and after the database schema has been created, upgraded or downgraded as necessary. + * If the database connection must be configured in some way before the schema + * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. + *

+ * + * @param db The database. + */ + public void onOpen(SupportSQLiteDatabase db) { + + } + + /** + * The method invoked when database corruption is detected. Default implementation will + * delete the database file. + * + * @param db the {@link SupportSQLiteDatabase} object representing the database on which + * corruption is detected. + */ + public void onCorruption(SupportSQLiteDatabase db) { + // the following implementation is taken from {@link DefaultDatabaseErrorHandler}. + + HiLog.error(label, "Corruption reported by sqlite on database: %{default}s", db.getPath()); + // is the corruption detected even before database could be 'opened'? + if (!db.isOpen()) { + // database files are not even openable. delete this database file. + // NOTE if the database has attached databases, then any of them could be corrupt. + // and not deleting all of them could cause corrupted database file to remain and + // make the application crash on database open operation. To avoid this problem, + // the application should provide its own {@link DatabaseErrorHandler} impl class + // to delete ALL files of the database (including the attached databases). + deleteDatabaseFile(db.getPath()); + return; + } + + List> attachedDbs = null; + try { + // Close the database, which will cause subsequent operations to fail. + // before that, get the attached database list first. + try { + attachedDbs = db.getAttachedDbs(); + } catch (SQLiteException e) { + /* ignore */ + } + try { + db.close(); + } catch (IOException e) { + /* ignore */ + } + } finally { + // Delete all files of this corrupt database and/or attached databases + if (attachedDbs != null) { + for (Pair p : attachedDbs) { + deleteDatabaseFile(p.s); + } + } else { + // attachedDbs = null is possible when the database is so corrupt that even + // "PRAGMA database_list;" also fails. delete the main database file + deleteDatabaseFile(db.getPath()); + } + } + } + + private void deleteDatabaseFile(String fileName) { + if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) { + return; + } + HiLog.warn(label, "deleting the database file: %{private}s",fileName); + try { + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + DatabaseHelper databaseHelper = new DatabaseHelper(); + databaseHelper.deleteRdbStore(fileName); //TODO build + } else {*/ + try { + final boolean deleted = new File(fileName).delete(); + if (!deleted) { + HiLog.error(label, "Could not delete the database file %{private}s",fileName); + } + } catch (Exception error) { + HiLog.error(label, "error while deleting corrupted database file %{private}s", error); + } + // } + } catch (Exception e) { + /* print warning and ignore exception */ + HiLog.warn(label, "delete failed: ", e); + } + } + } + + /** + * The configuration to create an SQLite open helper object using {@link Factory}. + */ + @SuppressWarnings("WeakerAccess") + class Configuration { + /** + * Context to use to open or create the database. + */ + public final Context context; + /** + * Name of the database file, or null for an in-memory database. + */ + public final String name; + /** + * The callback class to handle creation, upgrade and downgrade. + */ + public final Callback callback; + + Configuration(Context context, String name, Callback callback) { + this.context = context; + this.name = name; + this.callback = callback; + } + + /** + * Creates a new Configuration.Builder to create an instance of Configuration. + * + * @param context to use to open or create the database. + * @return builder + */ + public static Builder builder(Context context) { + return new Builder(context); + } + + /** + * Builder class for {@link Configuration}. + */ + public static class Builder { + Context mContext; + String mName; + Callback mCallback; + + public Configuration build() { + if (mCallback == null) { + throw new IllegalArgumentException("Must set a callback to create the" + + " configuration."); + } + if (mContext == null) { + throw new IllegalArgumentException("Must set a non-null context to create" + + " the configuration."); + } + return new Configuration(mContext, mName, mCallback); + } + + Builder(Context context) { + mContext = context; + } + + /** + * Returns the builder for given name + * @param name Name of the database file, or null for an in-memory database. + * @return This + */ + public Builder name(String name) { + mName = name; + return this; + } + + /** + * Returns the builder for given callback + * @param callback The callback class to handle creation, upgrade and downgrade. + * @return this + */ + public Builder callback(Callback callback) { + mCallback = callback; + return this; + } + } + } + + /** + * Factory class to create instances of {@link SupportSQLiteOpenHelper} using + * {@link Configuration}. + */ + interface Factory { + /** + * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration. + * + * @param configuration The configuration to use while creating the open helper. + * + * @return A SupportSQLiteOpenHelper which can be used to open a database. + */ + SupportSQLiteOpenHelper create(Configuration configuration); + } +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteProgram.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteProgram.java new file mode 100644 index 0000000000000000000000000000000000000000..24fb6161577cade8ce71a116d005a577f4b5fd1e --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteProgram.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +import java.io.Closeable; + +@SuppressWarnings("unused") +public interface SupportSQLiteProgram extends Closeable { + /** + * Bind a NULL value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind null to + */ + void bindNull(int index); + + /** + * Bind a long value to this statement. The value remains bound until + * {@link #clearBindings} is called. + *addToBindArgs + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + void bindLong(int index, long value); + + /** + * Bind a double value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + void bindDouble(int index, double value); + + /** + * Bind a String value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + void bindString(int index, String value); + + /** + * Bind a byte array value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + void bindBlob(int index, byte[] value); + + /** + * Clears all existing bindings. Unset bindings are treated as NULL. + */ + void clearBindings(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQuery.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..0d7ab0ac81f590f5b8e7b62a9bd1009eed7f2445 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQuery.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +public interface SupportSQLiteQuery { + /** + * The SQL query. This query can have placeholders(?) for bind arguments. + * + * @return The SQL query to compile + */ + String getSql(); + + /** + * Callback to bind the query parameters to the compiled statement. + * + * @param statement The compiled statement + */ + void bindTo(SupportSQLiteProgram statement); + + /** + * Returns the number of arguments in this query. This is equal to the number of placeholders + * in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details. + * + * @return The number of arguments in the query. + */ + int getArgCount(); +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQueryBuilder.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..0ff03b36e1b87ea56f5526127e75fac89506b1d6 --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteQueryBuilder.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +import java.util.regex.Pattern; + +/** + * A simple query builder to create SQL SELECT queries. + */ +@SuppressWarnings("unused") +public final class SupportSQLiteQueryBuilder { + private static final Pattern sLimitPattern = + Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); + + private boolean mDistinct = false; + private final String mTable; + private String[] mColumns = null; + private String mSelection; + private Object[] mBindArgs; + private String mGroupBy = null; + private String mHaving = null; + private String mOrderBy = null; + private String mLimit = null; + + /** + * Creates a query for the given table name. + * + * @param tableName The table name(s) to query. + * + * @return A builder to create a query. + */ + public static SupportSQLiteQueryBuilder builder(String tableName) { + return new SupportSQLiteQueryBuilder(tableName); + } + + private SupportSQLiteQueryBuilder(String table) { + mTable = table; + } + + /** + * Adds DISTINCT keyword to the query. + * + * @return this + */ + public SupportSQLiteQueryBuilder distinct() { + mDistinct = true; + return this; + } + + /** + * Sets the given list of columns as the columns that will be returned. + * + * @param columns The list of column names that should be returned. + * + * @return this + */ + public SupportSQLiteQueryBuilder columns(String[] columns) { + mColumns = columns; + return this; + } + + /** + * Sets the arguments for the WHERE clause. + * + * @param selection The list of selection columns + * @param bindArgs The list of bind arguments to match against these columns + * + * @return this + */ + public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) { + mSelection = selection; + mBindArgs = bindArgs; + return this; + } + + /** + * Adds a GROUP BY statement. + * + * @param groupBy The value of the GROUP BY statement. + * + * @return this + */ + @SuppressWarnings("WeakerAccess") + public SupportSQLiteQueryBuilder groupBy(String groupBy) { + mGroupBy = groupBy; + return this; + } + + /** + * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work. + * + * @param having The having clause. + * + * @return this + */ + public SupportSQLiteQueryBuilder having(String having) { + mHaving = having; + return this; + } + + /** + * Adds an ORDER BY statement. + * + * @param orderBy The order clause. + * + * @return this + */ + public SupportSQLiteQueryBuilder orderBy(String orderBy) { + mOrderBy = orderBy; + return this; + } + + /** + * Adds a LIMIT statement. + * + * @param limit The limit value. + * + * @return this + */ + public SupportSQLiteQueryBuilder limit(String limit) { + if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { + throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); + } + mLimit = limit; + return this; + } + + /** + * Creates the {@link SupportSQLiteQuery} that can be passed into + * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}. + * + * @return a new query + */ + public SupportSQLiteQuery create() { + if (isEmpty(mGroupBy) && !isEmpty(mHaving)) { + throw new IllegalArgumentException( + "HAVING clauses are only permitted when using a groupBy clause"); + } + StringBuilder query = new StringBuilder(120); + + query.append("SELECT "); + if (mDistinct) { + query.append("DISTINCT "); + } + if (mColumns != null && mColumns.length != 0) { + appendColumns(query, mColumns); + } else { + query.append(" * "); + } + query.append(" FROM "); + query.append(mTable); + appendClause(query, " WHERE ", mSelection); + appendClause(query, " GROUP BY ", mGroupBy); + appendClause(query, " HAVING ", mHaving); + appendClause(query, " ORDER BY ", mOrderBy); + appendClause(query, " LIMIT ", mLimit); + + return new SimpleSQLiteQuery(query.toString(), mBindArgs); + } + + private static void appendClause(StringBuilder s, String name, String clause) { + if (!isEmpty(clause)) { + s.append(name); + s.append(clause); + } + } + + /** + * Add the names that are non-null in columns to s, separating + * them with commas. + * @param s sting builder + * @param columns columns + */ + private static void appendColumns(StringBuilder s, String[] columns) { + int n = columns.length; + + for (int i = 0; i < n; i++) { + String column = columns[i]; + if (i > 0) { + s.append(", "); + } + s.append(column); + } + s.append(' '); + } + + private static boolean isEmpty(String input) { + return input == null || input.length() == 0; + } +} + diff --git a/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteStatement.java b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..4c1b543e03fe34e4ef7ea7ec50b53e893479d57e --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/harmonyx/SupportSQLiteStatement.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.harmonyx; + +@SuppressWarnings("unused") +public interface SupportSQLiteStatement extends SupportSQLiteProgram { + /** + * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example + * CREATE / DROP table, view, trigger, index etc. + * + * @throws net.sqlcipher.SQLException If the SQL string is invalid for + * some reason + */ + void execute(); + + /** + * Execute this SQL statement, if the the number of rows affected by execution of this SQL + * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. + * + * @return the number of rows affected by this SQL statement execution. + * @throws net.sqlcipher.SQLException If the SQL string is invalid for + * some reason + */ + int executeUpdateDelete(); + + /** + * Execute this SQL statement and return the ID of the row inserted due to this call. + * The SQL statement should be an INSERT for this to be a useful call. + * + * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. + * + * @throws net.sqlcipher.SQLException If the SQL string is invalid for + * some reason + */ + long executeInsert(); + + /** + * Execute a statement that returns a 1 by 1 table with a numeric value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws net.sqlcipher.sqlite.SQLiteDoneException if the query returns zero rows + */ + long simpleQueryForLong(); + /** + * Execute a statement that returns a 1 by 1 table with a text value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws net.sqlcipher.sqlite.SQLiteDoneException if the query returns zero rows + */ + String simpleQueryForString(); +} diff --git a/sqlcipher/src/main/java/net/sqlcipher/utils/TextUtils.java b/sqlcipher/src/main/java/net/sqlcipher/utils/TextUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..a3d39152d9166239bcd18da9824d32e5626446ba --- /dev/null +++ b/sqlcipher/src/main/java/net/sqlcipher/utils/TextUtils.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.utils; + +public class TextUtils { + public static boolean isEmpty(CharSequence str) { + return str == null || str.length() == 0; + } +} diff --git a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherDatabase.kt b/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherDatabase.kt deleted file mode 100644 index a55b75ade4151a4be86212ed87ebf905f71413a6..0000000000000000000000000000000000000000 --- a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherDatabase.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.dbflow5.sqlcipher - -import android.content.ContentValues -import com.dbflow5.database.AndroidDatabaseWrapper -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.FlowCursor -import net.sqlcipher.database.SQLiteDatabase -import net.sqlcipher.database.SQLiteException - -/** - * Description: Implements the code necessary to use a [SQLiteDatabase] in dbflow. - */ -class SQLCipherDatabase -internal constructor(val database: SQLiteDatabase) : AndroidDatabaseWrapper { - - override val version: Int - get() = database.version - - override val isInTransaction: Boolean - get() = database.inTransaction() - - override fun execSQL(query: String) = rethrowDBFlowException { - database.execSQL(query) - } - - override fun beginTransaction() { - database.beginTransaction() - } - - override fun setTransactionSuccessful() { - database.setTransactionSuccessful() - } - - override fun endTransaction() { - database.endTransaction() - } - - override fun compileStatement(rawQuery: String): DatabaseStatement = rethrowDBFlowException { - SQLCipherStatement.from(database.compileStatement(rawQuery)) - } - - override fun rawQuery(query: String, selectionArgs: Array?): FlowCursor = rethrowDBFlowException { - FlowCursor.from(database.rawQuery(query, selectionArgs)) - } - - override fun updateWithOnConflict(tableName: String, - contentValues: ContentValues, - where: String?, whereArgs: Array?, - conflictAlgorithm: Int): Long = rethrowDBFlowException { - database.updateWithOnConflict(tableName, contentValues, where, whereArgs, conflictAlgorithm).toLong() - } - - override fun insertWithOnConflict(tableName: String, - nullColumnHack: String?, - values: ContentValues, - sqLiteDatabaseAlgorithmInt: Int): Long = rethrowDBFlowException { - database.insertWithOnConflict(tableName, nullColumnHack, values, sqLiteDatabaseAlgorithmInt) - } - - override fun query(tableName: String, - columns: Array?, - selection: String?, - selectionArgs: Array?, - groupBy: String?, - having: String?, - orderBy: String?): FlowCursor = rethrowDBFlowException { - FlowCursor.from(database.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy)) - } - - override fun delete(tableName: String, - whereClause: String?, - whereArgs: Array?): Int = rethrowDBFlowException { - database.delete(tableName, whereClause, whereArgs) - } - - companion object { - - @JvmStatic - fun from(database: SQLiteDatabase): SQLCipherDatabase = SQLCipherDatabase(database) - } -} - -fun SQLiteException.toDBFlowSQLiteException() = com.dbflow5.database.SQLiteException("A Database Error Occurred", this) - -inline fun rethrowDBFlowException(fn: () -> T) = try { - fn() -} catch (e: SQLiteException) { - throw e.toDBFlowSQLiteException() -} \ No newline at end of file diff --git a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherOpenHelper.kt b/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherOpenHelper.kt deleted file mode 100644 index 1fb99c29db1c178cede64e6ec5ef3951c53ba47c..0000000000000000000000000000000000000000 --- a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherOpenHelper.kt +++ /dev/null @@ -1,177 +0,0 @@ -package com.dbflow5.sqlcipher - -import android.content.Context -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.DatabaseConfig -import com.dbflow5.config.OpenHelperCreator -import com.dbflow5.database.AndroidMigrationFileHelper -import com.dbflow5.database.DatabaseCallback -import com.dbflow5.database.DatabaseHelper -import com.dbflow5.database.DatabaseHelperDelegate -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.database.OpenHelper -import net.sqlcipher.database.SQLiteDatabase -import net.sqlcipher.database.SQLiteOpenHelper - -/** - * Description: The replacement [OpenHelper] for SQLCipher. Specify a subclass of this is [DatabaseConfig.getDatabaseClass] - * of your database to get it to work with specifying the secret you use for the databaseForTable. - */ -abstract class SQLCipherOpenHelper( - private val context: Context, - databaseDefinition: DBFlowDatabase, listener: DatabaseCallback?) - : SQLiteOpenHelper(context, - if (databaseDefinition.isInMemory) null else databaseDefinition.databaseFileName, - null, databaseDefinition.databaseVersion), OpenHelper { - - final override val delegate: DatabaseHelperDelegate - private var cipherDatabase: SQLCipherDatabase? = null - private val _databaseName = databaseDefinition.databaseFileName - - override val isDatabaseIntegrityOk: Boolean - get() = delegate.isDatabaseIntegrityOk - - override val database: DatabaseWrapper - get() { - if (cipherDatabase == null || !cipherDatabase!!.database.isOpen) { - cipherDatabase = SQLCipherDatabase.from(getWritableDatabase(cipherSecret)) - } - return cipherDatabase!! - } - - /** - * @return The SQLCipher secret for opening this database. - */ - protected abstract val cipherSecret: String - - init { - SQLiteDatabase.loadLibs(context) - delegate = DatabaseHelperDelegate(context, listener, databaseDefinition, if (databaseDefinition.backupEnabled()) { - // Temp database mirrors existing - BackupHelper(context, - DatabaseHelperDelegate.getTempDbFileName(databaseDefinition), - databaseDefinition.databaseVersion, databaseDefinition) - } else null) - } - - override fun performRestoreFromBackup() { - delegate.performRestoreFromBackup() - } - - override fun backupDB() { - delegate.backupDB() - } - - /** - * Set a listener to listen for specific DB events and perform an action before we execute this classes - * specific methods. - * - * @param callback - */ - override fun setDatabaseListener(callback: DatabaseCallback?) { - delegate.setDatabaseHelperListener(callback) - } - - override fun onConfigure(db: SQLiteDatabase) { - delegate.onConfigure(SQLCipherDatabase.from(db)) - } - - override fun onCreate(db: SQLiteDatabase) { - delegate.onCreate(SQLCipherDatabase.from(db)) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - delegate.onUpgrade(SQLCipherDatabase.from(db), oldVersion, newVersion) - } - - override fun onOpen(db: SQLiteDatabase) { - delegate.onOpen(SQLCipherDatabase.from(db)) - } - - override fun setWriteAheadLoggingEnabled(enabled: Boolean) { - if (enabled) { - cipherDatabase?.database?.enableWriteAheadLogging() - } else { - cipherDatabase?.database?.disableWriteAheadLogging() - } - } - - override fun closeDB() { - database - cipherDatabase?.database?.close() - } - - override fun deleteDB() { - context.deleteDatabase(_databaseName) - } - - /** - * Simple helper to manage backup. - */ - private inner class BackupHelper(private val context: Context, - name: String, - version: Int, - databaseDefinition: DBFlowDatabase) - : SQLiteOpenHelper(context, name, null, version), OpenHelper { - - private var sqlCipherDatabase: SQLCipherDatabase? = null - private val databaseHelper: DatabaseHelper = DatabaseHelper(AndroidMigrationFileHelper(context), databaseDefinition) - private val _databaseName = databaseDefinition.databaseFileName - - override val database: DatabaseWrapper - get() { - if (sqlCipherDatabase == null) { - sqlCipherDatabase = SQLCipherDatabase.from(getWritableDatabase(cipherSecret)) - } - return sqlCipherDatabase!! - } - - override val delegate: DatabaseHelperDelegate? = null - - override val isDatabaseIntegrityOk: Boolean = false - - override fun performRestoreFromBackup() = Unit - - override fun backupDB() = Unit - - override fun closeDB() = Unit - - override fun setDatabaseListener(callback: DatabaseCallback?) = Unit - - override fun onConfigure(db: SQLiteDatabase) { - databaseHelper.onConfigure(SQLCipherDatabase.from(db)) - } - - override fun onCreate(db: SQLiteDatabase) { - databaseHelper.onCreate(SQLCipherDatabase.from(db)) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - databaseHelper.onUpgrade(SQLCipherDatabase.from(db), oldVersion, newVersion) - } - - override fun onOpen(db: SQLiteDatabase) { - databaseHelper.onOpen(SQLCipherDatabase.from(db)) - } - - override fun setWriteAheadLoggingEnabled(enabled: Boolean) = Unit - - override fun deleteDB() { - context.deleteDatabase(_databaseName) - } - } - - companion object { - /** - * Provides a handy interface for [OpenHelperCreator] usage. - */ - @JvmStatic - fun createHelperCreator(context: Context, secret: String): OpenHelperCreator = - OpenHelperCreator { db, callback -> - object : SQLCipherOpenHelper(context, db, callback) { - override val cipherSecret: String = secret - } - } - } - -} diff --git a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherStatement.kt b/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherStatement.kt deleted file mode 100644 index b2f49bb7fe3cafb81148309e1d903c2ff0df1433..0000000000000000000000000000000000000000 --- a/sqlcipher/src/main/kotlin/com/dbflow5/sqlcipher/SQLCipherStatement.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.dbflow5.sqlcipher - -import com.dbflow5.database.BaseDatabaseStatement -import com.dbflow5.database.DatabaseStatement - -import net.sqlcipher.database.SQLiteStatement - -/** - * Description: Implements the methods necessary for [DatabaseStatement]. Delegates calls to - * the contained [SQLiteStatement]. - */ -class SQLCipherStatement -internal constructor(val statement: SQLiteStatement) : BaseDatabaseStatement() { - - override fun executeUpdateDelete(): Long = rethrowDBFlowException { statement.executeUpdateDelete().toLong() } - - override fun execute() { - statement.execute() - } - - override fun close() { - statement.close() - } - - override fun simpleQueryForLong(): Long = rethrowDBFlowException { statement.simpleQueryForLong() } - - override fun simpleQueryForString(): String? = rethrowDBFlowException { statement.simpleQueryForString() } - - override fun executeInsert(): Long = rethrowDBFlowException { statement.executeInsert() } - - override fun bindString(index: Int, s: String) { - statement.bindString(index, s) - } - - override fun bindNull(index: Int) { - statement.bindNull(index) - } - - override fun bindLong(index: Int, aLong: Long) { - statement.bindLong(index, aLong) - } - - override fun bindDouble(index: Int, aDouble: Double) { - statement.bindDouble(index, aDouble) - } - - override fun bindBlob(index: Int, bytes: ByteArray) { - statement.bindBlob(index, bytes) - } - - override fun bindAllArgsAsStrings(selectionArgs: Array?) { - if (selectionArgs != null) { - for (i in selectionArgs.size downTo 1) { - bindString(i, selectionArgs[i - 1]) - } - } - } - - companion object { - - @JvmStatic - fun from(statement: SQLiteStatement): SQLCipherStatement = SQLCipherStatement(statement) - } -} diff --git a/sqlcipher/src/main/resources/base/element/string.json b/sqlcipher/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..fbb0057052c2a6e914acc443795a8b4508dd5a58 --- /dev/null +++ b/sqlcipher/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "sqlcipher_library", + "value": "sqlcipher_library" + } + ] +} diff --git a/sqlcipher/src/test/java/com/dbflow5/sqlcipher/ExampleTest.java b/sqlcipher/src/test/java/com/dbflow5/sqlcipher/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3c864ee52af1f3e9804c1346635711a268b827e0 --- /dev/null +++ b/sqlcipher/src/test/java/com/dbflow5/sqlcipher/ExampleTest.java @@ -0,0 +1,9 @@ +package com.dbflow5.sqlcipher; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts deleted file mode 100644 index 8b19a5542d1f3b823602ddac1e6a4e1f8cb4765a..0000000000000000000000000000000000000000 --- a/tests/build.gradle.kts +++ /dev/null @@ -1,84 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("com.android.application") - kotlin("android") - id("com.getkeepsafe.dexcount") - kotlin("kapt") -} - -android { - - useLibrary("org.apache.http.legacy") - - compileSdkVersion(Versions.TargetSdk) - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion(Versions.MinSdkRX) - targetSdkVersion(Versions.TargetSdk) - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - packagingOptions { - exclude("META-INF/services/javax.annotation.processing.Processor") - exclude("META-INF/rxjava.properties") - exclude("META-INF/DEPENDENCIES") - exclude("META-INF/LICENSE") - exclude("META-INF/LICENSE.txt") - exclude("META-INF/license.txt") - exclude("META-INF/NOTICE") - exclude("META-INF/NOTICE.txt") - exclude("META-INF/notice.txt") - exclude("META-INF/AL2.0") - exclude("META-INF/LGPL2.1") - exclude("META-INF/*.kotlin_module") - } - - sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - } -} - -dependencies { - implementation("androidx.appcompat:appcompat:1.2.0") - implementation(project(":lib")) - implementation(project(":sqlcipher")) - implementation(project(":reactive-streams")) - implementation(project(":contentprovider")) - implementation(project(":coroutines")) - implementation(project(":paging")) - implementation(project(":livedata")) - - kaptAndroidTest(project(":processor")) - - androidTestImplementation(Dependencies.JavaXAnnotation) - androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") { - exclude(group = "org.jetbrains.kotlin") - } - androidTestImplementation("org.mockito:mockito-android:2.23.0") - - androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2") - androidTestImplementation(Dependencies.JUnit) - androidTestImplementation("androidx.test:core:1.3.0") - androidTestImplementation("androidx.test:runner:1.3.0") - androidTestImplementation("androidx.test:rules:1.3.0") - androidTestImplementation("androidx.arch.core:core-testing:2.1.0") - androidTestImplementation("androidx.test.ext:junit:1.1.2") - -} - -dexcount { - includeClasses = true - orderByMethodCount = true -} - -tasks.withType().all { - kotlinOptions.freeCompilerArgs += listOf("-XXLanguage:+InlineClasses", "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") -} \ No newline at end of file diff --git a/tests/gradle.properties b/tests/gradle.properties deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/tests/proguard-rules.pro b/tests/proguard-rules.pro deleted file mode 100644 index fb5e279715d9fb8ea60080253a8b42abb6ff26ad..0000000000000000000000000000000000000000 --- a/tests/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/andrewgrosner/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/tests/src/androidTest/AndroidManifest.xml b/tests/src/androidTest/AndroidManifest.xml deleted file mode 100644 index 089d54e81d08e821698121779b91ab08297276df..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/tests/src/androidTest/java/com/dbflow5/BaseUnitTest.kt b/tests/src/androidTest/java/com/dbflow5/BaseUnitTest.kt deleted file mode 100644 index db986649d4ddde43a8c94a8443bd6859a51c1b0e..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/BaseUnitTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dbflow5 - -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Rule -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -abstract class BaseUnitTest { - - @JvmField - @Rule - var dblflowTestRule = DBFlowInstrumentedTestRule.create() - - val context: Context - get() = ApplicationProvider.getApplicationContext() - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/DBFlowInstrumentedTestRule.kt b/tests/src/androidTest/java/com/dbflow5/DBFlowInstrumentedTestRule.kt deleted file mode 100644 index cc28d12918127c841e55c8eca0aaafa5586bbf8d..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/DBFlowInstrumentedTestRule.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.FlowConfig -import com.dbflow5.config.FlowLog -import com.dbflow5.config.FlowManager -import com.dbflow5.contentobserver.ContentObserverDatabase -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.runtime.ContentResolverNotifier -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement - - -class DBFlowInstrumentedTestRule(private val dbConfigBlock: FlowConfig.Builder.() -> Unit) : TestRule { - - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { - - @Throws(Throwable::class) - override fun evaluate() { - FlowLog.setMinimumLoggingLevel(FlowLog.Level.V) - FlowManager.init(DemoApp.context) { - database({ - transactionManagerCreator(::ImmediateTransactionManager) - }, AndroidSQLiteOpenHelper.createHelperCreator(DemoApp.context)) - dbConfigBlock() - } - try { - base.evaluate() - } finally { - FlowManager.destroy() - } - } - } - } - - companion object { - fun create(dbConfigBlock: FlowConfig.Builder.() -> Unit = {}) = DBFlowInstrumentedTestRule(dbConfigBlock) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/ImmediateTransactionManager.kt b/tests/src/androidTest/java/com/dbflow5/ImmediateTransactionManager.kt deleted file mode 100644 index 33cec2c365d0863a7c87c701f22927b644162db4..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/ImmediateTransactionManager.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.transaction.BaseTransactionManager -import com.dbflow5.transaction.ITransactionQueue -import com.dbflow5.transaction.Transaction - -/** - * Description: Executes all transactions on same thread for testing. - */ -class ImmediateTransactionManager(databaseDefinition: DBFlowDatabase) - : BaseTransactionManager(ImmediateTransactionQueue(), databaseDefinition) - - -class ImmediateTransactionQueue : ITransactionQueue { - - override fun add(transaction: Transaction) { - transaction.newBuilder() - .runCallbacksOnSameThread(true) - .build() - .executeSync() - } - - override fun cancel(transaction: Transaction) { - - } - - override fun startIfNotAlive() { - } - - override fun cancel(name: String) { - } - - override fun quit() { - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/TestDatabase.kt b/tests/src/androidTest/java/com/dbflow5/TestDatabase.kt deleted file mode 100644 index b7ed235d03b447eed6ebd60956d96de1b61df9ef..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/TestDatabase.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.annotation.Database -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.Migration -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.migration.BaseMigration -import com.dbflow5.migration.UpdateTableMigration -import com.dbflow5.models.SimpleModel - -/** - * Description: - */ -@Database(version = 1) -abstract class TestDatabase : DBFlowDatabase() { - - @Migration(version = 1, database = TestDatabase::class, priority = 5) - class TestMigration : UpdateTableMigration(SimpleModel::class.java) { - override fun onPreMigrate() { - super.onPreMigrate() - set(SimpleModel_Table.name.eq("Test")).where(SimpleModel_Table.name.eq("Test1")) - } - } - - @Migration(version = 1, database = TestDatabase::class, priority = 1) - class SecondMigration : BaseMigration() { - override fun migrate(database: DatabaseWrapper) { - - } - } -} - -@Database(version = 1, foreignKeyConstraintsEnforced = true) -abstract class TestForeignKeyDatabase : DBFlowDatabase() { - - @Table(database = TestForeignKeyDatabase::class) - data class SimpleModel(@PrimaryKey var name: String = "") - - @Table(database = TestForeignKeyDatabase::class) - data class SimpleForeignModel(@PrimaryKey var id: Int = 0, - @ForeignKey var model: SimpleModel? = null) -} diff --git a/tests/src/androidTest/java/com/dbflow5/TestExtensions.kt b/tests/src/androidTest/java/com/dbflow5/TestExtensions.kt deleted file mode 100644 index 378b2ba747546d739ecdc2dcf7e966402a4c480f..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/TestExtensions.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.sql.Query -import org.junit.Assert.assertEquals -import org.junit.Assert.fail -import kotlin.reflect.KClass - - -fun String.assertEquals(query: Query) = assertEquals(this, query.query.trim()) - -fun Query.assertEquals(actual: Query) = assertEquals(query.trim(), actual.query.trim()) - -fun assertThrowsException(expectedException: KClass, function: () -> Unit) { - try { - function() - fail("Expected call to fail. Unexpectedly passed") - } catch (e: Exception) { - if (e.javaClass != expectedException.java) { - e.printStackTrace() - fail("Expected $expectedException but got ${e.javaClass}") - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/TestForeignKeyDatabaseTest.kt b/tests/src/androidTest/java/com/dbflow5/TestForeignKeyDatabaseTest.kt deleted file mode 100644 index e258e8ce6b011a7aa95a65d61b444480350cc559..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/TestForeignKeyDatabaseTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.config.database -import org.junit.Test - -class TestForeignKeyDatabaseTest : BaseUnitTest() { - - @Test - fun verifyDB() { - database { db -> - val enabled = longForQuery(db, "PRAGMA foreign_keys;") - assert(enabled == 1L) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/User.kt b/tests/src/androidTest/java/com/dbflow5/User.kt deleted file mode 100644 index 6dc795f32feda5d87917315f63e6d570a4758051..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/User.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.dbflow5 - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.contentobserver.ContentObserverDatabase - -@Table(database = ContentObserverDatabase::class, name = "User2") -class User(@PrimaryKey var id: Int = 0, - @Column var firstName: String? = null, - @Column var lastName: String? = null, - @Column var email: String? = null) diff --git a/tests/src/androidTest/java/com/dbflow5/config/ConfigIntegrationTest.kt b/tests/src/androidTest/java/com/dbflow5/config/ConfigIntegrationTest.kt deleted file mode 100644 index 2a23166416406fba076fbd53ca421ef391c4a62e..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/config/ConfigIntegrationTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.dbflow5.config - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.adapter.queriable.ListModelLoader -import com.dbflow5.adapter.queriable.SingleModelLoader -import com.dbflow5.adapter.saveable.ModelSaver -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.models.SimpleModel -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test - -/** - * Description: - */ -class ConfigIntegrationTest : BaseUnitTest() { - - @Before - fun setup() { - FlowManager.reset() - FlowLog.setMinimumLoggingLevel(FlowLog.Level.V) - } - - - @Test - fun test_flowConfig() { - val config = flowConfig(context) { - openDatabasesOnInit(true) - } - assertEquals(config.openDatabasesOnInit, true) - assertTrue(config.databaseConfigMap.isEmpty()) - assertTrue(config.databaseHolders.isEmpty()) - } - - @Test - fun test_tableConfig() { - - val customListModelLoader = ListModelLoader(SimpleModel::class.java) - val singleModelLoader = SingleModelLoader(SimpleModel::class.java) - val modelSaver = ModelSaver() - - FlowManager.init( - flowConfig(context) { - database({ - table { - singleModelLoader(singleModelLoader) - listModelLoader(customListModelLoader) - modelAdapterModelSaver(modelSaver) - } - }, AndroidSQLiteOpenHelper.createHelperCreator(context)) - }) - - val flowConfig = FlowManager.getConfig() - assertNotNull(flowConfig) - - val databaseConfig = flowConfig.databaseConfigMap[TestDatabase::class.java] as DatabaseConfig - assertNotNull(databaseConfig) - - - val config = databaseConfig.tableConfigMap[SimpleModel::class.java] as TableConfig - assertNotNull(config) - - assertEquals(config.listModelLoader, customListModelLoader) - assertEquals(config.singleModelLoader, singleModelLoader) - - val modelAdapter = SimpleModel::class.modelAdapter - assertEquals(modelAdapter.listModelLoader, customListModelLoader) - assertEquals(modelAdapter.singleModelLoader, singleModelLoader) - assertEquals(modelAdapter.modelSaver, modelSaver) - } - -} - - diff --git a/tests/src/androidTest/java/com/dbflow5/config/DatabaseConfigTest.kt b/tests/src/androidTest/java/com/dbflow5/config/DatabaseConfigTest.kt deleted file mode 100644 index ed259e4d89ea672b9b84d2f7767f0787078334de..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/config/DatabaseConfigTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.dbflow5.config - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.database.DatabaseCallback -import com.dbflow5.database.OpenHelper -import com.dbflow5.transaction.BaseTransactionManager -import com.nhaarman.mockitokotlin2.mock -import org.junit.Assert -import org.junit.Before -import org.junit.Test - - -/** - * Description: - */ -class DatabaseConfigTest : BaseUnitTest() { - - private lateinit var builder: FlowConfig.Builder - - @Before - fun setup() { - FlowManager.reset() - FlowLog.setMinimumLoggingLevel(FlowLog.Level.V) - builder = FlowConfig.Builder(context) - } - - @Test - fun test_databaseConfig() { - - val helperListener = object : DatabaseCallback {} - val customOpenHelper = mock() - - val openHelperCreator = OpenHelperCreator { _, _ -> - customOpenHelper - } - var testTransactionManager: TestTransactionManager? = null - val managerCreator = TransactionManagerCreator { databaseDefinition -> - testTransactionManager = TestTransactionManager(databaseDefinition) - testTransactionManager!! - } - - FlowManager.init(builder.apply { - database({ - databaseName("Test") - helperListener(helperListener) - transactionManagerCreator(managerCreator) - }, openHelperCreator) - }.build()) - - val flowConfig = FlowManager.getConfig() - Assert.assertNotNull(flowConfig) - - val databaseConfig = flowConfig.databaseConfigMap[TestDatabase::class.java]!! - Assert.assertEquals("Test", databaseConfig.databaseName) - Assert.assertEquals(".db", databaseConfig.databaseExtensionName) - Assert.assertEquals(databaseConfig.transactionManagerCreator, managerCreator) - Assert.assertEquals(databaseConfig.databaseClass, TestDatabase::class.java) - Assert.assertEquals(databaseConfig.openHelperCreator, openHelperCreator) - Assert.assertEquals(databaseConfig.callback, helperListener) - Assert.assertTrue(databaseConfig.tableConfigMap.isEmpty()) - - - val databaseDefinition = database() - Assert.assertEquals(databaseDefinition.transactionManager, testTransactionManager) - Assert.assertEquals(databaseDefinition.openHelper, customOpenHelper) - } - - @Test - fun test_EmptyName() { - FlowManager.init(builder.apply { - database({ - databaseName("Test") - extensionName("") - }, AndroidSQLiteOpenHelper.createHelperCreator(context)) - }.build()) - - val databaseConfig = FlowManager.getConfig().databaseConfigMap[TestDatabase::class.java]!! - Assert.assertEquals("Test", databaseConfig.databaseName) - Assert.assertEquals("", databaseConfig.databaseExtensionName) - } - -} - -class TestTransactionManager(databaseDefinition: DBFlowDatabase) - : BaseTransactionManager(mock(), databaseDefinition) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.kt b/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.kt deleted file mode 100644 index 3fa7cfa47242ddb0b12584c327eac182de3ed6a3..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverDatabase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.dbflow5.contentobserver - -import com.dbflow5.annotation.Database -import com.dbflow5.config.DBFlowDatabase - -@Database(version = 1) -abstract class ContentObserverDatabase : DBFlowDatabase() diff --git a/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverTest.kt b/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverTest.kt deleted file mode 100644 index c84d1f8dcb7b8e0f6391fbe022933e6bab44b56c..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/contentobserver/ContentObserverTest.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.dbflow5.contentobserver - -import android.net.Uri -import androidx.test.core.app.ApplicationProvider -import com.dbflow5.DBFlowInstrumentedTestRule -import com.dbflow5.DemoApp -import com.dbflow5.ImmediateTransactionManager -import com.dbflow5.TABLE_QUERY_PARAM -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.database -import com.dbflow5.config.databaseForTable -import com.dbflow5.config.modelAdapter -import com.dbflow5.config.tableName -import com.dbflow5.contentobserver.User_Table.id -import com.dbflow5.contentobserver.User_Table.name -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.getNotificationUri -import com.dbflow5.query.SQLOperator -import com.dbflow5.query.delete -import com.dbflow5.runtime.ContentResolverNotifier -import com.dbflow5.runtime.FlowContentObserver -import com.dbflow5.structure.ChangeAction -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import java.util.concurrent.CountDownLatch - -class ContentObserverTest { - - @JvmField - @Rule - var dblflowTestRule = DBFlowInstrumentedTestRule.create { - database({ - modelNotifier(ContentResolverNotifier(DemoApp.context, "com.grosner.content")) - transactionManagerCreator { databaseDefinition: DBFlowDatabase -> - ImmediateTransactionManager(databaseDefinition) - } - }, AndroidSQLiteOpenHelper.createHelperCreator(ApplicationProvider.getApplicationContext())) - } - - val contentUri = "com.grosner.content" - - private lateinit var user: User - - @Before - fun setupUser() { - database { dbFlowDatabase -> - delete().execute(dbFlowDatabase) - } - user = User(5, "Something", 55) - } - - @Test - fun testSpecificUris() { - val conditionGroup = User::class.modelAdapter - .getPrimaryConditionClause(user) - val uri = getNotificationUri(contentUri, - User::class.java, ChangeAction.DELETE, - conditionGroup.conditions.toTypedArray()) - - assertEquals(uri.authority, contentUri) - assertEquals(tableName(), uri.getQueryParameter(TABLE_QUERY_PARAM)) - assertEquals(uri.fragment, ChangeAction.DELETE.name) - assertEquals(Uri.decode(uri.getQueryParameter(Uri.encode(id.query))), "5") - assertEquals(Uri.decode(uri.getQueryParameter(Uri.encode(name.query))), "Something") - } - - @Test - fun testSpecificUrlInsert() { - //assertProperConditions(ChangeAction.INSERT) { user, db -> user.insert(db) } - } - - @Test - fun testSpecificUrlUpdate() { - // assertProperConditions(ChangeAction.UPDATE) { user, db -> user.apply { age = 56 }.update(db) } - - } - - @Test - fun testSpecificUrlSave() { - // insert on SAVE - //assertProperConditions(ChangeAction.INSERT) { user, db -> user.apply { age = 57 }.save(db) } - } - - @Test - fun testSpecificUrlDelete() { - // user.save(databaseForTable()) - // assertProperConditions(ChangeAction.DELETE) { user, db -> user.delete(db) } - } - - private fun assertProperConditions(action: ChangeAction, userFunc: (User, DatabaseWrapper) -> Unit) { - val contentObserver = FlowContentObserver(contentUri) - val countDownLatch = CountDownLatch(1) - val mockOnModelStateChangedListener = MockOnModelStateChangedListener(countDownLatch) - contentObserver.addModelChangeListener(mockOnModelStateChangedListener) - contentObserver.registerForContentChanges(DemoApp.context, User::class.java) - - userFunc(user, databaseForTable()) - countDownLatch.await() - - val ops = mockOnModelStateChangedListener.operators!! - assertEquals(2, ops.size) - assertEquals(ops[0].columnName(), id.query) - assertEquals(ops[1].columnName(), name.query) - assertEquals(ops[1].value(), "Something") - assertEquals(ops[0].value(), "5") - assertEquals(action, mockOnModelStateChangedListener.action) - - contentObserver.removeModelChangeListener(mockOnModelStateChangedListener) - contentObserver.unregisterForContentChanges(DemoApp.context) - } - - class MockOnModelStateChangedListener(val countDownLatch: CountDownLatch) - : FlowContentObserver.OnModelStateChangedListener { - - var action: ChangeAction? = null - var operators: Array? = null - - - override fun onModelStateChanged(table: Class<*>?, action: ChangeAction, - primaryKeyValues: Array) { - this.action = action - operators = primaryKeyValues - countDownLatch.countDown() - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/contentobserver/User.kt b/tests/src/androidTest/java/com/dbflow5/contentobserver/User.kt deleted file mode 100644 index 15e1e9b3a94f361fb23916b5efe1435abe42b300..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/contentobserver/User.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dbflow5.contentobserver - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table - -@Table(database = ContentObserverDatabase::class) -class User(@PrimaryKey var id: Int = 0, @PrimaryKey var name: String = "", @Column var age: Int = 0) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/database/transaction/CoroutinesTest.kt b/tests/src/androidTest/java/com/dbflow5/database/transaction/CoroutinesTest.kt deleted file mode 100644 index 70b86e7b56a719050b9ee76cd05e267812c90b02..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/database/transaction/CoroutinesTest.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.dbflow5.database.transaction - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.coroutines.awaitDelete -import com.dbflow5.coroutines.awaitInsert -import com.dbflow5.coroutines.awaitSave -import com.dbflow5.coroutines.awaitTransact -import com.dbflow5.coroutines.awaitUpdate -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table -import com.dbflow5.query.delete -import com.dbflow5.query.select -import com.dbflow5.structure.save -import kotlinx.coroutines.runBlocking -import org.junit.Test - -/** - * Description: - */ -class CoroutinesTest : BaseUnitTest() { - - @Test - fun testTransact() { - runBlocking { - database { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - - val query = (select from SimpleModel::class - where SimpleModel_Table.name.eq("5")) - .awaitTransact(db) { queryList(it) } - - assert(query.size == 1) - - - val result = (delete() - where SimpleModel_Table.name.eq("5")) - .awaitTransact(db) { executeUpdateDelete(it) } - assert(result == 1L) - } - } - } - - @Test - fun testAwaitSaveAndDelete() { - runBlocking { - database { db -> - val simpleModel = SimpleModel("Name") - val result = simpleModel.awaitSave(db) - assert(result) - - assert(simpleModel.awaitDelete(db)) - } - } - } - - @Test - fun testAwaitInsertAndDelete() { - runBlocking { - database { db -> - val simpleModel = SimpleModel("Name") - val result = simpleModel.awaitInsert(db) - assert(result > 0) - assert(simpleModel.awaitDelete(db)) - } - } - } - - @Test - fun testAwaitUpdate() { - runBlocking { - database { db -> - val simpleModel = TwoColumnModel(name = "Name", id = 5) - val result = simpleModel.awaitSave(db) - assert(result) - - simpleModel.id = 5 - val updated = simpleModel.awaitUpdate(db) - assert(updated) - - val loadedModel = (select from TwoColumnModel::class - where TwoColumnModel_Table.id.eq(5)) - .awaitTransact(db) { querySingle(it) } - assert(loadedModel?.id == 5) - } - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/database/transaction/FastStoreModelTransactionTest.kt b/tests/src/androidTest/java/com/dbflow5/database/transaction/FastStoreModelTransactionTest.kt deleted file mode 100644 index 6cd134e9c0471e0c9951dc76aa15511d5b6f0cbc..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/database/transaction/FastStoreModelTransactionTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.dbflow5.database.transaction - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.coroutines.awaitInsert -import com.dbflow5.coroutines.awaitSave -import com.dbflow5.coroutines.awaitUpdate -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.query.select -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals -import org.junit.Test -import java.util.* - -class FastStoreModelTransactionTest : BaseUnitTest() { - - @Test - fun testSaveBuilder() { - runBlocking { - database(TestDatabase::class) { db -> - val result = (0..9) - .map { SimpleModel("$it") }.awaitSave(db) - val list = (select from SimpleModel::class).queryList(db) - assertEquals(10, list.size) - assertEquals(10L, result) - } - } - } - - @Test - fun testInsertBuilder() { - runBlocking { - database(TestDatabase::class) { db -> - val result = (0..9) - .map { SimpleModel("$it") }.awaitInsert(db) - val list = (select from SimpleModel::class).queryList(db) - assertEquals(10, list.size) - assertEquals(10L, result) - } - } - } - - @Test - fun testUpdateBuilder() { - runBlocking { - database(TestDatabase::class) { db -> - val oldList = (0..9).map { TwoColumnModel("$it", Random().nextInt()) } - oldList.awaitInsert(db) - - (0..9).map { TwoColumnModel("$it", Random().nextInt()) }.awaitUpdate(db) - - val list = (select from TwoColumnModel::class).queryList(db) - assertEquals(10, list.size) - list.forEachIndexed { index, model -> - assertNotEquals(model, oldList[index]) - } - } - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataModels.kt b/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataModels.kt deleted file mode 100644 index 308ac8a64f41831fe2a1de14aaaf8a143d7c11a6..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataModels.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.dbflow5.livedata - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table - -/** - * Description: Basic live data object model. - */ -@Table(database = TestDatabase::class) -data class LiveDataModel(@PrimaryKey var id: String = "", - var name: Int = 0) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataTest.kt b/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataTest.kt deleted file mode 100644 index e884b82de6d3ac75a4feff0e50bb79f0ee9030ba..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/livedata/LiveDataTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.dbflow5.livedata - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.Observer -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.query.select -import com.dbflow5.structure.insert -import com.nhaarman.mockitokotlin2.mock -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestRule - -/** - * Description: - */ -class LiveDataTest : BaseUnitTest() { - - @get:Rule - val rule: TestRule = InstantTaskExecutorRule() - - @Test - fun live_data_executes_for_a_few_model_queries() { - val data = (select from LiveDataModel::class) - .toLiveData { db -> queryList(db) } - - val observer = mock>>() - val lifecycle = LifecycleRegistry(mock()) - lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) - - data.observeForever(observer) - - val value = data.value!! - assert(value.isEmpty()) - - database() - .beginTransactionAsync { db -> - (0..2).forEach { - LiveDataModel(id = "$it", name = it).insert(db) - } - } - .execute() - - database().tableObserver.checkForTableUpdates() - - val value2 = data.value!! - assert(value2.size == 3) { "expected ${value2.size} == 3" } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/migration/MigrationModels.kt b/tests/src/androidTest/java/com/dbflow5/migration/MigrationModels.kt deleted file mode 100644 index 34fcc66566d0af77e51a686293b1c734eaabd762..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/migration/MigrationModels.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dbflow5.migration - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Migration -import com.dbflow5.database.DatabaseWrapper - -@Migration(database = TestDatabase::class, priority = 1, version = 1) -class FirstMigration : BaseMigration() { - override fun migrate(database: DatabaseWrapper) { - - } -} - -@Migration(database = TestDatabase::class, priority = 2, version = 1) -class SecondMigration : BaseMigration() { - override fun migrate(database: DatabaseWrapper) { - - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/migration/UpdateTableMigrationTest.kt b/tests/src/androidTest/java/com/dbflow5/migration/UpdateTableMigrationTest.kt deleted file mode 100644 index 41f5416c3b463273fc7e3d5c7da52ee2cffd9e31..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/migration/UpdateTableMigrationTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.dbflow5.migration - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import org.junit.Test - -/** - * Description: - */ - -class UpdateTableMigrationTest : BaseUnitTest() { - - - @Test - fun testUpdateMigrationQuery() { - val update = UpdateTableMigration(SimpleModel::class.java) - update.set(SimpleModel_Table.name.eq("yes")) - update.migrate(databaseForTable()) - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/models/AutoIncrementTest.kt b/tests/src/androidTest/java/com/dbflow5/models/AutoIncrementTest.kt deleted file mode 100644 index 57307fb9650ad31bc40011ada2c2636c610d8c6d..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/AutoIncrementTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.databaseForTable -import com.dbflow5.structure.insert -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: - */ -class AutoIncrementTest : BaseUnitTest() { - - @Test - fun testCanInsertAutoIncrement() { - val model = AutoIncrementingModel() - model.insert(databaseForTable()) - assertEquals(1L, model.id) - } - - @Test - fun testCanInsertExistingIdAutoIncrement() { - val model = AutoIncrementingModel(3) - model.insert(databaseForTable()) - assertEquals(3L, model.id) - } -} - - -@Table(database = TestDatabase::class) -class AutoIncrementingModel(@PrimaryKey(autoincrement = true) var id: Long = 0) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/CachingModels.kt b/tests/src/androidTest/java/com/dbflow5/models/CachingModels.kt deleted file mode 100644 index 040a042eeabca5abb1cd57142fdc073ed7b82c72..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/CachingModels.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.MultiCacheField -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.query.cache.MultiKeyCacheConverter - -@Table(database = TestDatabase::class, cachingEnabled = true) -class SimpleCacheObject(@PrimaryKey var id: String = "") - -@Table(database = TestDatabase::class, cachingEnabled = true) -class Coordinate(@PrimaryKey var latitude: Double = 0.0, - @PrimaryKey var longitude: Double = 0.0, - @ForeignKey var path: Path? = null) { - - companion object { - @JvmField - @MultiCacheField - val cacheConverter = MultiKeyCacheConverter { values -> "${values[0]},${values[1]}" } - } -} - -@Table(database = TestDatabase::class) -class Path(@PrimaryKey var id: String = "", - @Column var name: String = "") \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/CachingModelsTest.kt b/tests/src/androidTest/java/com/dbflow5/models/CachingModelsTest.kt deleted file mode 100644 index 9e2d29f0a87a39b84f7bb97ef6b4ac689d57dff7..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/CachingModelsTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.query.select -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals -import org.junit.Test - -/** - * Description: Tests to ensure caching works as expected. - */ -class CachingModelsTest : BaseUnitTest() { - - @Test - fun testSimpleCache() { - database { db -> - val list = arrayListOf() - (0..9).forEach { - val simpleCacheObject = SimpleCacheObject("$it") - simpleCacheObject.save(db) - list += simpleCacheObject - } - - val loadedList = (select from SimpleCacheObject::class).queryList(db) - - loadedList.forEachIndexed { index, simpleCacheObject -> - assertEquals(list[index], simpleCacheObject) - } - } - } - - @Test - fun testComplexObject() { - database { db -> - val path = Path("1", "Path") - path.save(db) - - val coordinate = Coordinate(40.5, 84.0, path) - coordinate.save(db) - - val oldPath = coordinate.path - - val loadedCoordinate = (select from Coordinate::class).querySingle(db)!! - assertEquals(coordinate, loadedCoordinate) - - // we want to ensure relationships reloaded. - assertNotEquals(oldPath, loadedCoordinate.path) - } - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAO.kt b/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAO.kt deleted file mode 100644 index a4399622a1f3a014ad547b01e719184fd2bc7cc1..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAO.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.config.modelAdapter -import com.dbflow5.coroutines.defer -import com.dbflow5.paging.QueryDataSource -import com.dbflow5.paging.toDataSourceFactory -import com.dbflow5.query.Where -import com.dbflow5.query.select -import com.dbflow5.reactivestreams.transaction.asSingle -import com.dbflow5.transaction.Transaction -import io.reactivex.rxjava3.core.Single -import kotlinx.coroutines.Deferred - -/** - * Create this class in your own database module. - */ -interface DBProvider { - - val database: T - -} - -interface CurrencyDAO : DBProvider { - - fun coroutineStoreUSD(currency: Currency): Deferred = - database.beginTransactionAsync { db -> - modelAdapter().save(currency, db) - }.defer() - - /** - * Utilize coroutines package - */ - fun coroutineRetrieveUSD(): Deferred> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")).queryList(it) - }.defer() - - fun rxStoreUSD(currency: Currency): Single = - database.beginTransactionAsync { db -> - modelAdapter().save(currency, db) - }.asSingle() - - /** - * Utilize RXJava2 package. - * Also can use asMaybe(), or asFlowable() (to register for changes and continue listening) - */ - fun rxRetrieveUSD(): Single> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - }.asSingle() - - /** - * Utilize Vanilla Transactions. - */ - fun retrieveUSD(): Transaction.Builder> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - } - - /** - * Utilize Paging Library from paging artifact. - */ - fun pagingRetrieveUSD(): QueryDataSource.Factory> = (select from Currency::class - where (Currency_Table.symbol eq "$")) - .toDataSourceFactory(database) - -} - -fun provideCurrencyDAO(db: TestDatabase) = object : CurrencyDAO { - override val database: TestDatabase = db -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAOTest.kt b/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAOTest.kt deleted file mode 100644 index c7c0146b2109f2bdfcd0f3e684fc3a02c400f71c..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/CurrencyDAOTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.rx2.RXTestRule -import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -class CurrencyDAOTest : BaseUnitTest() { - - lateinit var currencyDAO: CurrencyDAO - - val currency = Currency(symbol = "$", name = "United States Dollar", shortName = "USD") - - @Rule - @JvmField - val rxTestRule = RXTestRule() - - @Before - fun setupTest() { - currencyDAO = object : CurrencyDAO { - override val database: TestDatabase = database() - } - } - - @Test - fun validateCoroutine() = runBlockingTest { - val success = currencyDAO.coroutineStoreUSD(currency).await() - assert(success) { "Currency didn't save" } - val result = currencyDAO.coroutineRetrieveUSD().await() - assert(result.size == 1) { "Results list was empty" } - assert(result[0] == currency) { "Expected ${currency} but got ${result[0]}" } - } - - @Test - fun validateRx() { - val success = currencyDAO.rxStoreUSD(currency).blockingGet() - assert(success) { "Currency didn't save" } - val result = currencyDAO.rxRetrieveUSD().blockingGet() - assert(result.size == 1) { "Results list was empty" } - assert(result[0] == currency) { "Expected ${currency} but got ${result[0]}" } - } - - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/DontCreateModelTest.kt b/tests/src/androidTest/java/com/dbflow5/models/DontCreateModelTest.kt deleted file mode 100644 index 728eed3edef89c820b0f678bb567f3f4d6534e42..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/DontCreateModelTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertThrowsException -import com.dbflow5.config.databaseForTable -import com.dbflow5.database.SQLiteException -import com.dbflow5.query.select -import org.junit.Test - -/** - * Description: - */ -class DontCreateModelTest : BaseUnitTest() { - - @Test - fun testModelNotCreated() { - databaseForTable { dbFlowDatabase -> - assertThrowsException(SQLiteException::class) { - (select from DontCreateModel::class).queryList(dbFlowDatabase) - } - } - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/models/ForeignKeyModels.kt b/tests/src/androidTest/java/com/dbflow5/models/ForeignKeyModels.kt deleted file mode 100644 index 927bfef67190605b055e3d7064635bbec89e2470..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ForeignKeyModels.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.ColumnMapReference -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ForeignKeyAction -import com.dbflow5.annotation.ForeignKeyReference -import com.dbflow5.annotation.NotNull -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.annotation.TypeConverter -import com.dbflow5.database.FlowCursor -import com.dbflow5.query.LoadFromCursorListener - -/** - * Example of simple foreign key object with one foreign key object. - */ -@Table(database = TestDatabase::class) -class Blog(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column var name: String = "", - @ForeignKey(saveForeignKeyModel = true) var author: Author? = null) - -/** - * Parent used as foreign key reference. - */ -@Table(database = TestDatabase::class) -class Author(@PrimaryKey(autoincrement = true) var id: Int = 0, - @Column(name = "first_name") var firstName: String = "", - @Column(name = "last_name") var lastName: String = "") - -/** - * Example of simple foreign key object with its [ForeignKey] deferred. - */ -@Table(database = TestDatabase::class) -class BlogDeferred(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column var name: String = "", - @ForeignKey(deferred = true) var author: Author? = null) - -/** - * Class has example of single foreign key with [ForeignKeyReference] specified - */ -@Table(database = TestDatabase::class) -class BlogRef(@PrimaryKey var id: Int = 0, @PrimaryKey var name: String = "", - @ForeignKey(references = arrayOf( - ForeignKeyReference(columnName = "authorId", foreignKeyColumnName = "id", - defaultValue = "not gonna work"))) - var author: Author? = null) - -/** - * Class has example of single foreign key with [ForeignKeyReference] specified that is not the model object. - */ -@Table(database = TestDatabase::class) -class BlogRefNoModel(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column var name: String = "", - @ForeignKey(references = arrayOf(ForeignKeyReference(columnName = "authorId", foreignKeyColumnName = "id", notNull = NotNull(onNullConflict = ConflictAction.FAIL))), - tableClass = Author::class) - var authorId: String? = null) - - -/** - * Class has example of single foreign key with [ForeignKeyReference] as [PrimaryKey] - */ -@Table(database = TestDatabase::class) -class BlogPrimary(@PrimaryKey @ForeignKey var author: Author? = null, @Column var id: Int = 0) - -/** - * Example of simple foreign key object with one foreign key object thats [ForeignKey.stubbedRelationship] - * and [ForeignKey.deleteForeignKeyModel] and [ForeignKey.saveForeignKeyModel] - */ -@Table(database = TestDatabase::class) -class BlogStubbed(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column var name: String = "", - @ForeignKey(stubbedRelationship = true, deleteForeignKeyModel = true, saveForeignKeyModel = true, - onDelete = ForeignKeyAction.CASCADE, onUpdate = ForeignKeyAction.RESTRICT) - var author: Author? = null, - var setter: String = "", - var getter: String = "") : LoadFromCursorListener { - override fun onLoadFromCursor(cursor: FlowCursor) { - - } -} - -class DoubleToDouble(val double: Double) - -@TypeConverter -class DoubleConverter : com.dbflow5.converter.TypeConverter() { - override fun getDBValue(model: DoubleToDouble?) = model?.double - - override fun getModelValue(data: Double?): DoubleToDouble? = data?.let { DoubleToDouble(data) } -} - -class Location(var latitude: DoubleToDouble? = DoubleToDouble(0.0), - var longitude: DoubleToDouble? = DoubleToDouble(0.0)) - -@Table(database = TestDatabase::class) -class Position(@PrimaryKey var id: Int = 0, @ColumnMap var location: Location? = null) - -@Table(database = TestDatabase::class) -class Position2(@PrimaryKey var id: Int = 0, - @ColumnMap(references = arrayOf( - ColumnMapReference(columnName = "latitude", columnMapFieldName = "latitude", - defaultValue = "40.6"), - ColumnMapReference(columnName = "longitude", columnMapFieldName = "longitude", - defaultValue = "55.5"))) - var location: Location? = null) - -class Location2(var latitude: DoubleToDouble? = DoubleToDouble(0.0), - var longitude: Double? = 0.0) - -@Table(database = TestDatabase::class) -class PositionWithTypeConverter(@PrimaryKey var id: Int = 0, - @ColumnMap(references = arrayOf(ColumnMapReference(columnName = "latitude", - columnMapFieldName = "latitude", typeConverter = DoubleConverter::class), - ColumnMapReference(columnName = "longitude", columnMapFieldName = "longitude"))) - var location: Location2? = null) - -@Table(database = TestDatabase::class) -class NotNullReferenceModel(@PrimaryKey var name: String = "", - @NotNull @ForeignKey var model: SimpleModel? = null) diff --git a/tests/src/androidTest/java/com/dbflow5/models/FtsModelTest.kt b/tests/src/androidTest/java/com/dbflow5/models/FtsModelTest.kt deleted file mode 100644 index 79cbdc6cd673ead07c746247b6e973f5707f3202..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/FtsModelTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.query.insert -import com.dbflow5.query.offsets -import com.dbflow5.query.property.docId -import com.dbflow5.query.property.tableName -import com.dbflow5.query.select -import com.dbflow5.query.snippet -import com.dbflow5.structure.save -import org.junit.Test - -/** - * Description: - */ -class FtsModelTest : BaseUnitTest() { - - @Test - fun validate_fts4_created() { - database { db -> - val model = Fts4Model(name = "FTSBABY") - model.save(db) - - val rows = (insert( - docId, - Fts4VirtualModel2_Table.name) select (select(Fts4Model_Table.id, Fts4Model_Table.name) from Fts4Model::class)) - .executeInsert(db) - assert(rows > 0) - - - } - } - - @Test - fun match_query() { - validate_fts4_created() - database { db -> - val model = (select from Fts4VirtualModel2::class where (tableName() match "FTSBABY")) - .querySingle(db) - assert(model != null) - } - } - - @Test - fun offsets_query() { - validate_fts4_created() - database { db -> - val value = (select(offsets()) from Fts4VirtualModel2::class - where (tableName() match "FTSBaby")) - .stringValue(db) - assert(value != null) - assert(value == "0 0 0 7") - } - } - - @Test - fun snippet_query() { - database { db -> - val model = Fts4Model(name = "During 30 Nov-1 Dec, 2-3oC drops. Cool in the upper portion, minimum temperature 14-16oC \n" + - " and cool elsewhere, minimum temperature 17-20oC. Cold to very cold on mountaintops, \n" + - " minimum temperature 6-12oC. Northeasterly winds 15-30 km/hr. After that, temperature \n" + - " increases. Northeasterly winds 15-30 km/hr. ") - model.save(db) - val rows = (insert( - docId, - Fts4VirtualModel2_Table.name) select (select(Fts4Model_Table.id, Fts4Model_Table.name) from Fts4Model::class)) - .executeInsert(db) - assert(rows > 0) - val value = (select(snippet( - start = "[", - end = "]", - ellipses = "...", - )) from Fts4VirtualModel2::class - where (tableName() match "\"min* tem*\"")) - .stringValue(db) - assert(value != null) - assert(value == "...the upper portion, [minimum] [temperature] 14-16oC \n" + - " and cool elsewhere, [minimum] [temperature] 17-20oC. Cold...") - } - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/models/IndexModels.kt b/tests/src/androidTest/java/com/dbflow5/models/IndexModels.kt deleted file mode 100644 index 74b8c4878234bc87fbee593fbe19f06b13ff0f58..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/IndexModels.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.Index -import com.dbflow5.annotation.IndexGroup -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import java.util.* - -/** - * Description: - */ - -@Table(database = TestDatabase::class, - indexGroups = [ - IndexGroup(number = 1, name = "firstIndex"), - IndexGroup(number = 2, name = "secondIndex"), - IndexGroup(number = 3, name = "thirdIndex"), - ]) -class IndexModel { - @Index(indexGroups = [1, 2, 3]) - @PrimaryKey - var id: Int = 0 - - @Index(indexGroups = [1]) - @Column - var first_name: String? = null - - @Index(indexGroups = [2]) - @Column - var last_name: String? = null - - @Index(indexGroups = [3]) - @Column - var created_date: Date? = null - - @Index(indexGroups = [2, 3]) - @Column - var isPro: Boolean = false -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/InnerClassExample.kt b/tests/src/androidTest/java/com/dbflow5/models/InnerClassExample.kt deleted file mode 100644 index 61255b4a4a9c4727ab80c9124b79f782aad733a9..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/InnerClassExample.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table - -/** - * Example ensuring static inner classes work. - */ -class Outer { - - @Table(database = TestDatabase::class) - class Inner(@PrimaryKey var id: Int = 0) -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ManyToMany.kt b/tests/src/androidTest/java/com/dbflow5/models/ManyToMany.kt deleted file mode 100644 index 0e190f51837d0b6298b06b9a39f757dce63cedb4..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ManyToMany.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ManyToMany -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table - -@ManyToMany(referencedTable = Song::class) -@Table(database = TestDatabase::class) -class Artist(@PrimaryKey(autoincrement = true) var id: Int = 0, - @Column var name: String = "") - -@Table(database = TestDatabase::class) -class Song(@PrimaryKey(autoincrement = true) var id: Int = 0, - @Column var name: String = "") \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ManyToManyTest.kt b/tests/src/androidTest/java/com/dbflow5/models/ManyToManyTest.kt deleted file mode 100644 index f0149bc82bf0fd08d1dfea095dd499d160afe1ab..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ManyToManyTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.structure.save -import org.junit.Assert.assertTrue -import org.junit.Test - -class ManyToManyTest : BaseUnitTest() { - - @Test - fun testCanCreateManyToMany() { - databaseForTable { db -> - val artist = Artist(name = "Andrew Grosner") - val song = Song(name = "Livin' on A Prayer") - - artist.save(db) - song.save(db) - - val artistSong = Artist_Song() - artistSong.artist = artist - artistSong.song = song - assertTrue(artistSong.save(db)) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ModelViewTest.kt b/tests/src/androidTest/java/com/dbflow5/models/ModelViewTest.kt deleted file mode 100644 index 4bb08138238250847c223bceebba0b8c95a8f24e..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ModelViewTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.java.JavaModelView -import org.junit.Test - -class ModelViewTest : BaseUnitTest() { - - @Test - fun validateModelViewQuery() { - "SELECT `id` AS `authorId`,`first_name` || ' ' || `last_name` AS `authorName` FROM `Author`" - .assertEquals(AuthorView.getQuery()) - } - - @Test - fun validateJavaModelViewQuery() { - "SELECT `first_name` AS `firstName`,`id` AS `id`".assertEquals(JavaModelView.getQuery()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ModelViews.kt b/tests/src/androidTest/java/com/dbflow5/models/ModelViews.kt deleted file mode 100644 index c629e8de113db80132016f953384f1edc5655fab..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ModelViews.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnMap -import com.dbflow5.annotation.ModelView -import com.dbflow5.annotation.ModelViewQuery -import com.dbflow5.models.Author_Table.* -import com.dbflow5.query.From -import com.dbflow5.query.property.IProperty -import com.dbflow5.query.property.property -import com.dbflow5.query.select - -class AuthorName(var name: String = "", var age: Int = 0) - - -@ModelView(database = TestDatabase::class) -class AuthorView(@Column var authorId: Int = 0, @Column var authorName: String = "", - @ColumnMap var author: AuthorName? = null) { - - companion object { - @JvmStatic - @ModelViewQuery - fun getQuery(): From = (select(id.`as`("authorId"), - first_name.concatenate(" ".property as IProperty>) - .concatenate(last_name as IProperty>) - .`as`("authorName")) - from Author::class) - } -} - -@ModelView(database = TestDatabase::class, priority = 2, allFields = true) -class PriorityView(var name: String = "") { - - companion object { - @JvmStatic - @get:ModelViewQuery - val query: From = select((first_name + last_name).`as`("name")) from Author::class - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.kt b/tests/src/androidTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.kt deleted file mode 100644 index 00b92a96b3be8298cddee734e7fedd65e1a83a9f..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/NonTypical/nonTypicalClassName.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.dbflow5.models.NonTypical - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table - -/** - * Tests package name capitalized, class name is lower cased. - */ -@Table(database = TestDatabase::class) -class nonTypicalClassName(@PrimaryKey var id: Int = 0) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/OneToManyModelTest.kt b/tests/src/androidTest/java/com/dbflow5/models/OneToManyModelTest.kt deleted file mode 100644 index a31fc67fb81ebb16a7d3ef28fa9f7848f2dd86b7..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/OneToManyModelTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.query.select -import com.dbflow5.structure.delete -import com.dbflow5.structure.exists -import com.dbflow5.structure.save -import org.junit.Assert.* -import org.junit.Test - -class OneToManyModelTest : BaseUnitTest() { - - @Test - fun testOneToManyModel() { - database(TestDatabase::class) { db -> - var testModel2 = TwoColumnModel("Greater", 4) - testModel2.save(db) - - testModel2 = TwoColumnModel("Lesser", 1) - testModel2.save(db) - - // assert we save - var oneToManyModel = OneToManyModel("HasOrders") - oneToManyModel.save(db) - assertTrue(oneToManyModel.exists(db)) - - // assert loading works as expected. - oneToManyModel = (select from OneToManyModel::class).requireSingle(db) - assertNotNull(oneToManyModel.getRelatedOrders(db)) - assertTrue(!oneToManyModel.getRelatedOrders(db).isEmpty()) - - // assert the deletion cleared the variable - oneToManyModel.delete(db) - assertFalse(oneToManyModel.exists(db)) - assertNull(oneToManyModel.orders) - - // assert singular relationship was deleted. - val list = (select from TwoColumnModel::class).queryList(db) - assertTrue(list.size == 1) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/OneToManyModels.kt b/tests/src/androidTest/java/com/dbflow5/models/OneToManyModels.kt deleted file mode 100644 index 1f3bb0cde2b143245d32333708b9eeb14724cb9f..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/OneToManyModels.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.OneToMany -import com.dbflow5.annotation.OneToManyMethod -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.select -import com.dbflow5.structure.BaseModel -import com.dbflow5.structure.oneToMany - -@Table(database = TestDatabase::class) -class OneToManyModel(@PrimaryKey var name: String? = null) { - - var orders: List? = null - - var models: List? = null - - @get:OneToMany(oneToManyMethods = [OneToManyMethod.ALL]) - var simpleModels by oneToMany { select from OneToManyBaseModel::class } - - @get:OneToMany(oneToManyMethods = [OneToManyMethod.ALL]) - var setBaseModels by oneToMany { select from OneToManyBaseModel::class } - - @OneToMany(oneToManyMethods = [OneToManyMethod.ALL], - variableName = "orders", efficientMethods = false) - fun getRelatedOrders(wrapper: DatabaseWrapper): List { - var localOrders = orders - if (localOrders == null) { - localOrders = (select from TwoColumnModel::class where id.greaterThan(3)) - .queryList(wrapper) - } - orders = localOrders - return localOrders - } - - @OneToMany(oneToManyMethods = [OneToManyMethod.DELETE], - variableName = "models") - fun getRelatedModels(wrapper: DatabaseWrapper): List { - var localModels = models - if (localModels == null) { - localModels = (select from OneToManyBaseModel::class).queryList(wrapper) - } - models = localModels - return localModels - } - - -} - -@Table(database = TestDatabase::class) -class OneToManyBaseModel(@PrimaryKey var id: Int = 0) : BaseModel() \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ParentChildCachingTest.kt b/tests/src/androidTest/java/com/dbflow5/models/ParentChildCachingTest.kt deleted file mode 100644 index dd3a077acd03382d6378deb976c08cf3b9b8aced..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ParentChildCachingTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: - */ -class ParentChildCachingTest : BaseUnitTest() { - - - @Test - fun testCanLoadChildFromCache() { - database { db -> - val child = TestModelChild() - child.id = 1 - child.name = "Test child" - child.save(db) - - var parent = TestModelParent() - parent.id = 1 - parent.name = "Test parent" - parent.child = child - parent.save(db) - - parent = (select from TestModelParent::class).requireSingle(db) - var parentChild = parent.child!! - parentChild = parentChild.load(db)!! - - assertEquals(1, parentChild.id) - assertEquals("Test child", parentChild.name) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/ProspectQuiz.kt b/tests/src/androidTest/java/com/dbflow5/models/ProspectQuiz.kt deleted file mode 100644 index 6e9725060c9a79b86cb9293a1fed35aac9c73e35..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/ProspectQuiz.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ForeignKeyAction -import com.dbflow5.annotation.Index -import com.dbflow5.annotation.IndexGroup -import com.dbflow5.annotation.NotNull -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.annotation.TypeConverter -import com.dbflow5.structure.BaseModel - - -@TypeConverter -class MutableSetTypeConverter : com.dbflow5.converter.TypeConverter>() { - override fun getDBValue(model: MutableSet?): String? { - return model?.joinToString() - } - - override fun getModelValue(data: String?): MutableSet? { - return data?.split("")?.toMutableSet() - } -} - - -/* - * A quiz, consisting primarily of a question from the current user, and a set of responses from - * prospects and/or prior prospects that the user has acted upon. Though the author ID is needed on the server, the - * client has no need of it. Instead, just the canonical quiz identifier is used. - * It is envisioned that the resolvedResponses may be provided only when an additional parameter is - * specified in the request. - */ -@Table(database = TestDatabase::class, allFields = true, useBooleanGetterSetters = false, - indexGroups = [IndexGroup(number = 1, name = "modified")]) -open internal class ProspectQuiz : BaseModel { - @NotNull - @PrimaryKey - lateinit var ID: String - - @NotNull - var question: String - - @NotNull - var pendingResponseCount: Int - - @NotNull - var resolvedResponseCount: Int - - @NotNull - var newResponseCount: Int - - // We have a list of prospect IDs that have been invited but not answered. Just IDs to be used - // with determining who can still be added. - @NotNull - @Column(typeConverter = MutableSetTypeConverter::class) - var pendingUnanswered: MutableSet - - @Index(indexGroups = [1]) - @Column(defaultValue = "1L") - var modifiedDate: Long? = null - - constructor(quizID: String) : this() { - ID = quizID - } - - constructor() { - question = "" - pendingResponseCount = 0 - resolvedResponseCount = 0 - newResponseCount = 0 - pendingUnanswered = mutableSetOf() - } -} - -/** - * An element of a quiz, consisting of a user the quiz targeted and his/her response - * Status is only used on the quiz full view, in which profiles that have been rejected or matched - * will be included in the quiz, but shown separately. In the quiz full view, a participant whose - * account is deactivated or deleted should never be visible. When a quiz is refreshed and a participant - * deleted or deactivated the account, the entry for the deleted user will disappear from the response. - */ -@Table(database = TestDatabase::class, allFields = true, useBooleanGetterSetters = false, - indexGroups = [IndexGroup(number = 1, name = "quizid_answerts")]) -open internal class ProspectQuizEntry : BaseModel { - @PrimaryKey - @NotNull - lateinit var profileID: String - - @PrimaryKey - @NotNull - @Index(indexGroups = [1]) - @ForeignKey(stubbedRelationship = true, - tableClass = ProspectQuiz::class, - onDelete = ForeignKeyAction.CASCADE) - lateinit var quizID: String - - //@ForeignKey(saveForeignKeyModel = true) var photo: PhotoMedia? - var text: String? - var participantStatus: QuizParticipantStatus? - - @NotNull - var name: String - - @NotNull - @Index(indexGroups = [1]) - var answerEpoch: Long - - // naming thing (`new`) - //@NotNull @JvmField @Column(typeConverter = ObservableBooleanTypeConverter::class) - //var neww: ObservableBoolean = ObservableBoolean(false) - /*var new: Boolean - get() = neww.get() - set(value) { neww.set(value) }*/ - - constructor() { - //photo = null - text = null - participantStatus = null - name = "" - //new = false - answerEpoch = 0 - } -} - -enum class QuizParticipantStatus { - Pending, - Rejected, - Connected, - ; - - companion object { - fun fromCode(code: Int): QuizParticipantStatus { - return when (code) { - 0 -> Pending - 1 -> Rejected - 2 -> Connected - else -> throw IllegalArgumentException("Invalid raw int for QuizParticipantStatus") - } - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/QueryModelTest.kt b/tests/src/androidTest/java/com/dbflow5/models/QueryModelTest.kt deleted file mode 100644 index a44a5b2709f1e56596293022b405148c4eb1edf6..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/QueryModelTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.models.Author_Table.id -import com.dbflow5.models.Blog_Table.author_id -import com.dbflow5.models.Blog_Table.name -import com.dbflow5.query.select -import com.dbflow5.structure.exists -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: Tests to ensure we can load a Query model from the DB - */ -class QueryModelTest : BaseUnitTest() { - - @Test - fun testCanLoadAuthorBlogs() { - database { db -> - val author = Author(0, "Andrew", "Grosner") - author.save(db) - val blog = Blog(0, "My First Blog", author) - blog.save(db) - - assert(author.exists(db)) - assert(blog.exists(db)) - - val result = (select(name.withTable().`as`("blogName"), id.withTable().`as`("authorId"), - Blog_Table.id.withTable().`as`("blogId")) from Blog::class innerJoin - Author::class on (author_id.withTable() eq id.withTable())) - .queryCustomSingle(AuthorNameQuery::class.java, db)!! - assertEquals(author.id, result.authorId) - assertEquals(blog.id, result.blogId) - } - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/models/QueryModels.kt b/tests/src/androidTest/java/com/dbflow5/models/QueryModels.kt deleted file mode 100644 index 45e61487c7676f312a57868b421ba6b9233a138d..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/QueryModels.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.QueryModel -import com.dbflow5.converter.TypeConverter -import com.dbflow5.data.Blob - -@QueryModel(database = TestDatabase::class, allFields = true) -class AuthorNameQuery(var blogName: String = "", - var authorId: Int = 0, var blogId: Int = 0) - - -@QueryModel(database = TestDatabase::class) -class CustomBlobModel(@Column var myBlob: MyBlob? = null) { - - class MyBlob(val blob: ByteArray) - - @com.dbflow5.annotation.TypeConverter - class MyTypeConverter : TypeConverter() { - - override fun getDBValue(model: MyBlob?) = model?.let { Blob(model.blob) } - - override fun getModelValue(data: Blob?) = data?.blob?.let { MyBlob(it) } - } -} - -@QueryModel(database = TestDatabase::class, allFields = true) -class AllFieldsQueryModel(var fieldModel: String? = null) \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModels.kt b/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModels.kt deleted file mode 100644 index e3c3c75ae01de3265cf7beab864a09b6cdd27055..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModels.kt +++ /dev/null @@ -1,282 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.ColumnIgnore -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ForeignKeyAction -import com.dbflow5.annotation.Fts3 -import com.dbflow5.annotation.Fts4 -import com.dbflow5.annotation.ManyToMany -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.QueryModel -import com.dbflow5.annotation.Table -import com.dbflow5.annotation.Unique -import com.dbflow5.annotation.UniqueGroup -import com.dbflow5.converter.TypeConverter -import com.dbflow5.data.Blob -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.query.SQLiteStatementListener -import com.dbflow5.structure.BaseModel -import java.math.BigDecimal -import java.math.BigInteger -import java.util.* - - -/** - * Description: - */ -@Table(database = TestDatabase::class) -class SimpleModel(@PrimaryKey var name: String? = "") - -@QueryModel(database = TestDatabase::class) -class SimpleCustomModel(@Column var name: String? = "") - -@Table(database = TestDatabase::class) -class SimpleQuickCheckModel(@PrimaryKey(quickCheckAutoIncrement = true, autoincrement = true) var name: Int = 0) - -@Table(database = TestDatabase::class, insertConflict = ConflictAction.FAIL, updateConflict = ConflictAction.FAIL) -class NumberModel(@PrimaryKey var id: Int = 0) - -@Table(database = TestDatabase::class) -class CharModel(@PrimaryKey var id: Int = 0, @Column var exampleChar: Char? = null) - -@Table(database = TestDatabase::class) -class TwoColumnModel(@PrimaryKey var name: String? = "", @Column(defaultValue = "56") var id: Int = 0) - -@Table(database = TestDatabase::class, createWithDatabase = false) -class DontCreateModel(@PrimaryKey var id: Int = 0) - -enum class Difficulty { - EASY, - MEDIUM, - HARD -} - -@Table(database = TestDatabase::class) -class EnumModel(@PrimaryKey var id: Int = 0, @Column var difficulty: Difficulty? = Difficulty.EASY) - -@Table(database = TestDatabase::class, allFields = true) -open class AllFieldsModel(@PrimaryKey var name: String? = null, - var count: Int? = 0, - @Column(getterName = "getTruth") - var truth: Boolean = false, - internal val finalName: String = "", - @ColumnIgnore private val hidden: Int = 0) { - - companion object { - - // example field to ensure static not used. - var COUNT: Int = 0 - } -} - -@Table(database = TestDatabase::class, allFields = true) -class SubclassAllFields(@Column var order: Int = 0) : AllFieldsModel() - -@Table(database = TestDatabase::class, assignDefaultValuesFromCursor = false) -class DontAssignDefaultModel(@PrimaryKey var name: String? = null, - @Column(getterName = "getNullableBool") var nullableBool: Boolean? = null, - @Column var index: Int = 0) - -@Table(database = TestDatabase::class, orderedCursorLookUp = true) -class OrderCursorModel(@Column var age: Int = 0, @PrimaryKey var id: Int = 0, @Column var name: String? = "") - -@Table(database = TestDatabase::class) -class TypeConverterModel(@PrimaryKey var id: Int = 0, - @Column(typeConverter = BlobConverter::class) var opaqueData: ByteArray? = null, - @Column var blob: Blob? = null, - @Column(typeConverter = CustomTypeConverter::class) - @PrimaryKey var customType: CustomType? = null) - -@Table(database = TestDatabase::class) -class EnumTypeConverterModel(@PrimaryKey var id: Int = 0, - @Column var blob: Blob? = null, - @Column var byteArray: ByteArray? = null, - @Column(typeConverter = CustomEnumTypeConverter::class) - var difficulty: Difficulty = Difficulty.EASY) - -@Table(database = TestDatabase::class, allFields = true) -class FeedEntry(@PrimaryKey var id: Int = 0, - var title: String? = null, - var subtitle: String? = null) - -@Table(database = TestDatabase::class) -@ManyToMany( - generatedTableClassName = "Refund", referencedTable = Transfer::class, - referencedTableColumnName = "refund_in", thisTableColumnName = "refund_out", - saveForeignKeyModels = true -) -data class Transfer(@PrimaryKey var transfer_id: UUID = UUID.randomUUID()) - -@Table(database = TestDatabase::class) -data class Transfer2( - @PrimaryKey - var id: UUID = UUID.randomUUID(), - @ForeignKey(stubbedRelationship = true) - var origin: Account? = null -) - -@Table(database = TestDatabase::class) -data class Account(@PrimaryKey var id: UUID = UUID.randomUUID()) - -@Table(database = TestDatabase::class) -class SqlListenerModel(@PrimaryKey var id: Int = 0) : SQLiteStatementListener { - - override fun onBindToInsertStatement(databaseStatement: DatabaseStatement) { - TODO("not implemented") - } - - override fun onBindToUpdateStatement(databaseStatement: DatabaseStatement) { - TODO("not implemented") - } - - override fun onBindToDeleteStatement(databaseStatement: DatabaseStatement) { - TODO("not implemented") - } -} - -class CustomType(var name: Int? = 0) - -class CustomTypeConverter : TypeConverter() { - override fun getDBValue(model: CustomType?) = model?.name - - override fun getModelValue(data: Int?) = if (data == null) { - null - } else { - CustomType(data) - } - -} - -class CustomEnumTypeConverter : TypeConverter() { - override fun getDBValue(model: Difficulty?) = model?.name?.substring(0..0) - - override fun getModelValue(data: String?) = when (data) { - "E" -> Difficulty.EASY - "M" -> Difficulty.MEDIUM - "H" -> Difficulty.HARD - else -> Difficulty.HARD - } - -} - -@com.dbflow5.annotation.TypeConverter -class BlobConverter : TypeConverter() { - - override fun getDBValue(model: ByteArray?): Blob? { - return if (model == null) null else Blob(model) - } - - override fun getModelValue(data: Blob?): ByteArray? { - return data?.blob - } -} - -@Table(database = TestDatabase::class) -class DefaultModel(@PrimaryKey @Column(defaultValue = "5") var id: Int? = 0, - @Column(defaultValue = "5.0") var location: Double? = 0.0, - @Column(defaultValue = "\"String\"") var name: String? = "") - -@Table(database = TestDatabase::class, cachingEnabled = true) -class TestModelChild : BaseModel() { - @PrimaryKey - var id: Long = 0 - - @Column - var name: String? = null -} - -@Table(database = TestDatabase::class) -class TestModelParent : BaseModel() { - @PrimaryKey - var id: Long = 0 - - @Column - var name: String? = null - - @ForeignKey(stubbedRelationship = true) - var child: TestModelChild? = null -} - -@Table(database = TestDatabase::class) -class NullableNumbers(@PrimaryKey var id: Int = 0, - @Column var f: Float? = null, - @Column var d: Double? = null, - @Column var l: Long? = null, - @Column var i: Int? = null, - @Column var bigDecimal: BigDecimal? = null, - @Column var bigInteger: BigInteger? = null) - -@Table(database = TestDatabase::class) -class NonNullKotlinModel(@PrimaryKey var name: String = "", - @Column var date: Date = Date(), - @Column var numb: Int = 0) - -@Table(database = TestDatabase::class) -class Owner : BaseModel() { - @PrimaryKey(autoincrement = true) - var id: Int = 0 - - @Column - var name: String? = null -} - -@Table(database = TestDatabase::class) -class Dog : BaseModel() { - @ForeignKey(onDelete = ForeignKeyAction.CASCADE, stubbedRelationship = true) - var owner: Owner? = null - - @PrimaryKey(autoincrement = true) - var id: Int = 0 - - @Column - var name: String? = null -} - -@Table(database = TestDatabase::class) -data class Currency(@PrimaryKey(autoincrement = true) var id: Long = 0, - @Column @Unique var symbol: String? = null, - @Column var shortName: String? = null, - @Column @Unique var name: String = "") // nullability of fields are respected. We will not assign a null value to this field. - -inline class Password(val value: String) -inline class Email(val value: String) - -@Table(database = TestDatabase::class) -class UserInfo(@PrimaryKey - @set:JvmName("setEmail") - @get:JvmName("getEmail") - var email: Email, - @get:JvmName("getPassword") - @set:JvmName("setPassword") - var password: Password) { - constructor() : this(Email(""), Password("")) -} - - -@Table(database = TestDatabase::class) -class InternalClass internal constructor(@PrimaryKey - @get:JvmName("getId") - @set:JvmName("setId") - internal var id: String = "") - -@Table(database = TestDatabase::class, uniqueColumnGroups = [UniqueGroup(1)]) -class UniqueModel(@PrimaryKey var id: String = "", - @Unique(uniqueGroups = [1]) var name: String = "", - @ForeignKey @Unique(uniqueGroups = [1]) var model: TypeConverterModel? = null) - -@Table(database = TestDatabase::class) -@Fts3 -class Fts3Model(var name: String = "") - -@Table(database = TestDatabase::class) -class Fts4Model( - @PrimaryKey(autoincrement = true) - var id: Int = 0, - var name: String = "") - -@Table(database = TestDatabase::class) -@Fts4(contentTable = Fts4Model::class) -class Fts4VirtualModel2(var name: String = "") diff --git a/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModelsTest.kt b/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModelsTest.kt deleted file mode 100644 index 901bb8dda8995c4d78de51ddd817a2966d882267..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/SimpleTestModelsTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.modelAdapter -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: - */ -class SimpleTestModelsTest : BaseUnitTest() { - - @Test - fun validateCreationQuery() { - assertEquals("CREATE TABLE IF NOT EXISTS `TypeConverterModel`(" + - "`id` INTEGER, " + - "`opaqueData` BLOB, " + - "`blob` BLOB, " + - "`customType` INTEGER, " + - "PRIMARY KEY(`id`, `customType`))", modelAdapter().creationQuery) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/models/TempModelTest.kt b/tests/src/androidTest/java/com/dbflow5/models/TempModelTest.kt deleted file mode 100644 index b9b9a8cd61426b81ea1212bf84ab1a0632d48b61..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/models/TempModelTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.dbflow5.models - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.adapter.createIfNotExists -import com.dbflow5.adapter.drop -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.database -import com.dbflow5.config.modelAdapter -import com.dbflow5.structure.save -import org.junit.Test - - -@Table(database = TestDatabase::class, temporary = true, createWithDatabase = false) -class TempModel(@PrimaryKey var id: Int = 0) - -class TempModelTest: BaseUnitTest() { - - @Test - fun createTempTable() { - database { db -> - modelAdapter().createIfNotExists(db) - - TempModel(id = 5).save(db) - - modelAdapter().drop(db) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/paging/QueryDataSourceTest.kt b/tests/src/androidTest/java/com/dbflow5/paging/QueryDataSourceTest.kt deleted file mode 100644 index 81c4b07488abb76d0ad7edd14cc982c5b14bbea0..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/paging/QueryDataSourceTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.dbflow5.paging - -import androidx.paging.PagedList -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.assertThrowsException -import com.dbflow5.config.database -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.select -import com.dbflow5.query.set -import com.dbflow5.query.update -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * Description: - */ -class QueryDataSourceTest : BaseUnitTest() { - - @Test - fun testLoadInitialParams() { - database { db -> - (0 until 100).forEach { SimpleModel("$it").save(db) } - - val factory = (select from SimpleModel::class).toDataSourceFactory(db) - val list = PagedList.Builder(factory.create(), - PagedList.Config.Builder() - .setPageSize(3) - .setPrefetchDistance(6) - .setEnablePlaceholders(true).build()) - .setFetchExecutor { it.run() } // run on main - .setNotifyExecutor { it.run() } - .build() - - assertEquals(100, list.size) - - list.forEachIndexed { index, simpleModel -> - list.loadAround(index) - assertEquals(index, simpleModel.name?.toInt()) - - // assert we don't run over somehow. - assertTrue(index < 100) - } - } - } - - @Test - fun testThrowsErrorOnInvalidType() { - database { db -> - assertThrowsException(IllegalArgumentException::class) { - (update() set (SimpleModel_Table.name.eq("name"))) - .toDataSourceFactory(db) - .create() - } - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDB.kt b/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDB.kt deleted file mode 100644 index e374d5a4fea146887c046885d1dd82af27c675a6..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDB.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.dbflow5.prepackaged - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.Database -import com.dbflow5.annotation.Migration -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.migration.AlterTableMigration -import com.dbflow5.migration.BaseMigration -import com.dbflow5.sql.SQLiteType -import com.dbflow5.structure.BaseModel - -@Database(version = 1) -abstract class PrepackagedDB : DBFlowDatabase() - -@Database(version = 2) -abstract class MigratedPrepackagedDB : DBFlowDatabase() { - - @Migration(version = 2, database = MigratedPrepackagedDB::class, priority = 1) - class AddNewFieldMigration : AlterTableMigration(Dog2::class) { - override fun onPreMigrate() { - addColumn(SQLiteType.TEXT, "newField") - } - } - - @Migration(version = 2, database = MigratedPrepackagedDB::class, priority = 2) - class AddSomeDataMigration : BaseMigration() { - override fun migrate(database: DatabaseWrapper) { - Dog2(breed = "NewBreed", newField = "New Field Data").insert(database) - } - } - -} - -@Table(database = PrepackagedDB::class, allFields = true) -class Dog( - @PrimaryKey var id: Int = 0, - @Column var breed: String? = null, - @Column var color: String? = null, -) : BaseModel() - -@Table(database = MigratedPrepackagedDB::class, allFields = true, name = "Dog") -class Dog2( - @PrimaryKey var id: Int = 0, - @Column var breed: String? = null, - @Column var color: String? = null, - @Column var newField: String? = null, -) : BaseModel() diff --git a/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.kt b/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.kt deleted file mode 100644 index fdabc51c6823927c1db1fa56bdc0a31a02fb8c0d..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/prepackaged/PrepackagedDBTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.dbflow5.prepackaged - -import com.dbflow5.DBFlowInstrumentedTestRule -import com.dbflow5.DemoApp -import com.dbflow5.config.database -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.query.select -import org.junit.Assert.assertTrue -import org.junit.Rule -import org.junit.Test - -/** - * Description: Asserts our prepackaged DB loads. - */ -class PrepackagedDBTest { - - @JvmField - @Rule - var dblflowTestRule = DBFlowInstrumentedTestRule.create { - database({ - databaseName("prepackaged") - }, AndroidSQLiteOpenHelper.createHelperCreator(DemoApp.context)) - database({ - databaseName("prepackaged_2") - }, AndroidSQLiteOpenHelper.createHelperCreator(DemoApp.context)) - } - - @Test - fun assertWeCanLoadFromDB() { - database { db -> - val list = (select from Dog::class).queryList(db) - assertTrue(list.isNotEmpty()) - } - } - - @Test - fun assertWeCanLoadFromDBPostMigrate() { - database { db -> - val list = (select from Dog2::class).queryList(db) - assertTrue(list.isNotEmpty()) - - val newData = (select - from Dog2::class - where Dog2_Table.breed.eq("NewBreed") - and Dog2_Table.newField.eq("New Field Data")) - .querySingle(db) - assertTrue(newData != null) - } - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderObjects.kt b/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderObjects.kt deleted file mode 100644 index 8f77bef036eb5175754790406805f9457af16a91..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderObjects.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.dbflow5.provider - -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.Database -import com.dbflow5.annotation.ForeignKey -import com.dbflow5.annotation.ForeignKeyReference -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.contentprovider.annotation.ContentType -import com.dbflow5.contentprovider.annotation.ContentUri -import com.dbflow5.contentprovider.annotation.TableEndpoint - -/** - * Description: - */ -@ContentProvider(authority = ContentDatabase.AUTHORITY, database = ContentDatabase::class, - baseContentUri = ContentDatabase.BASE_CONTENT_URI) -@Database(version = ContentDatabase.VERSION) -abstract class ContentDatabase : ContentProviderDatabase() { - companion object { - const val BASE_CONTENT_URI = "content://" - - const val AUTHORITY = "com.grosner.content.test.ContentDatabase" - - const val VERSION = 1 - } -} - -@TableEndpoint(name = ContentProviderModel.NAME, contentProvider = ContentDatabase::class) -@Table(database = ContentDatabase::class, name = ContentProviderModel.NAME, generateContentValues = true) -class ContentProviderModel(@PrimaryKey(autoincrement = true) - var id: Long = 0, - @Column - var notes: String? = null, - @Column - var title: String? = null) : BaseProviderModel() { - - override val deleteUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val insertUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val updateUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val queryUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - companion object { - - const val NAME = "ContentProviderModel" - - @ContentUri(path = NAME, type = "${ContentType.VND_MULTIPLE}${NAME}") - val CONTENT_URI = ContentUtils.buildUriWithAuthority(ContentDatabase.AUTHORITY) - } -} - -@Table(database = ContentDatabase::class, generateContentValues = true) -class NoteModel(@PrimaryKey(autoincrement = true) - var id: Long = 0, - - @ForeignKey(references = arrayOf(ForeignKeyReference(columnName = "providerModel", - foreignKeyColumnName = "id"))) - var contentProviderModel: ContentProviderModel? = null, - - @Column - var note: String? = null, - - @Column - var isOpen: Boolean = false) : BaseProviderModel() { - - override val deleteUri get() = TestContentProvider.NoteModel.CONTENT_URI - - override val insertUri get() = TestContentProvider.NoteModel.CONTENT_URI - - override val updateUri get() = TestContentProvider.NoteModel.CONTENT_URI - - override val queryUri get() = TestContentProvider.NoteModel.CONTENT_URI -} - -@Table(database = ContentDatabase::class, generateContentValues = true) -class TestSyncableModel(@PrimaryKey(autoincrement = true) - var id: Long = 0, - @Column - var name: String? = null) : BaseSyncableProviderModel() { - - override val deleteUri get() = TestContentProvider.TestSyncableModel.CONTENT_URI - - override val insertUri get() = TestContentProvider.TestSyncableModel.CONTENT_URI - - override val updateUri get() = TestContentProvider.TestSyncableModel.CONTENT_URI - - override val queryUri get() = TestContentProvider.TestSyncableModel.CONTENT_URI -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderTests.kt b/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderTests.kt deleted file mode 100644 index 8487478f1f668da63bbfc63b0bb199c6aaf3f2fd..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/provider/ContentProviderTests.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentResolver -import androidx.test.core.app.ApplicationProvider -import androidx.test.rule.provider.ProviderTestRule -import com.dbflow5.DBFlowInstrumentedTestRule -import com.dbflow5.config.FlowManager -import com.dbflow5.config.database -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -/** - * Description: - */ -class ContentProviderTests { - - @JvmField - @Rule - var dblflowTestRule = DBFlowInstrumentedTestRule.create { - database({ - databaseName("content") - }, AndroidSQLiteOpenHelper.createHelperCreator(ApplicationProvider.getApplicationContext())) - } - - @get:Rule - val contentProviderRule = ProviderTestRule.Builder(TestContentProvider_Provider::class.java, TestContentProvider.AUTHORITY).build() - - private val mockContentResolver: ContentResolver - get() = contentProviderRule.resolver - - @Before - fun overrideContentResolver() { - FlowManager.globalContentResolver = mockContentResolver - } - - @Test - fun testContentProviderUtils() { - database(ContentDatabase::class) { db -> - listOf(NoteModel::class, ContentProviderModel::class).forEach { - com.dbflow5.query.delete(it).executeUpdateDelete(db) - } - - var contentProviderModel = ContentProviderModel() - contentProviderModel.notes = "Test" - var uri = ContentUtils.insert(mockContentResolver, TestContentProvider.ContentProviderModel.CONTENT_URI, contentProviderModel) - assertEquals(TestContentProvider.ContentProviderModel.CONTENT_URI.toString() + "/" + contentProviderModel.id, uri.toString()) - assertTrue(contentProviderModel.exists(db)) - - contentProviderModel.notes = "NewTest" - val update = ContentUtils.update(mockContentResolver, TestContentProvider.ContentProviderModel.CONTENT_URI, contentProviderModel) - assertEquals(update.toLong(), 1) - assertTrue(contentProviderModel.exists(db)) - contentProviderModel = contentProviderModel.load(db)!! - assertEquals("NewTest", contentProviderModel.notes) - - val noteModel = NoteModel() - noteModel.note = "Test" - noteModel.contentProviderModel = contentProviderModel - uri = ContentUtils.insert(mockContentResolver, TestContentProvider.NoteModel.CONTENT_URI, noteModel) - assertEquals(TestContentProvider.NoteModel.CONTENT_URI.toString() + "/" + noteModel.id, uri.toString()) - assertTrue(noteModel.exists(db)) - - assertTrue(ContentUtils.delete(mockContentResolver, TestContentProvider.NoteModel.CONTENT_URI, noteModel) > 0) - assertTrue(!noteModel.exists(db)) - - assertTrue(ContentUtils.delete(mockContentResolver, TestContentProvider.ContentProviderModel.CONTENT_URI, contentProviderModel) > 0) - assertTrue(!contentProviderModel.exists(db)) - - listOf(NoteModel::class, ContentProviderModel::class).forEach { - com.dbflow5.query.delete(it).executeUpdateDelete(db) - } - } - } - - @Test - fun testContentProviderNative() { - database(ContentDatabase::class) { db -> - listOf(NoteModel::class, ContentProviderModel::class).forEach { com.dbflow5.query.delete(it).executeUpdateDelete(db) } - - var contentProviderModel = ContentProviderModel(notes = "Test") - contentProviderModel.insert(db) - assertTrue(contentProviderModel.exists(db)) - - contentProviderModel.notes = "NewTest" - contentProviderModel.update(db) - contentProviderModel = contentProviderModel.load(db)!! - assertEquals("NewTest", contentProviderModel.notes) - - var noteModel = NoteModel(note = "Test", contentProviderModel = contentProviderModel) - noteModel.insert(db) - - noteModel.note = "NewTest" - noteModel.update(db) - noteModel = noteModel.load(db)!! - assertEquals("NewTest", noteModel.note) - - assertTrue(noteModel.exists(db)) - - noteModel.delete(db) - assertTrue(!noteModel.exists(db)) - - contentProviderModel.delete(db) - assertTrue(!contentProviderModel.exists(db)) - - listOf(NoteModel::class, ContentProviderModel::class).forEach { com.dbflow5.query.delete(it).executeUpdateDelete(db) } - } - } - - @Test - fun testSyncableModel() { - database(ContentDatabase::class) { db -> - com.dbflow5.query.delete().execute(db) - - var testSyncableModel = TestSyncableModel(name = "Name") - testSyncableModel.save(db) - - assertTrue(testSyncableModel.exists(db)) - - testSyncableModel.name = "TestName" - testSyncableModel.update(db) - assertEquals(testSyncableModel.name, "TestName") - - testSyncableModel = (select from TestSyncableModel::class - where (TestSyncableModel_Table.id.`is`(testSyncableModel.id))).querySingle(db)!! - - var fromContentProvider = TestSyncableModel(id = testSyncableModel.id) - fromContentProvider = fromContentProvider.load(db)!! - - assertEquals(fromContentProvider.name, testSyncableModel.name) - assertEquals(fromContentProvider.id, testSyncableModel.id) - - testSyncableModel.delete(db) - assertFalse(testSyncableModel.exists(db)) - - com.dbflow5.query.delete().execute(db) - } - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/provider/TestContentProvider.kt b/tests/src/androidTest/java/com/dbflow5/provider/TestContentProvider.kt deleted file mode 100644 index f2c7a182648f05d23e0fa4c5db056b71a7544cd3..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/provider/TestContentProvider.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.dbflow5.provider - -import android.content.ContentValues -import android.content.Context -import android.net.Uri -import com.dbflow5.contentprovider.annotation.ContentProvider -import com.dbflow5.contentprovider.annotation.ContentType -import com.dbflow5.contentprovider.annotation.ContentUri -import com.dbflow5.contentprovider.annotation.Notify -import com.dbflow5.contentprovider.annotation.NotifyMethod -import com.dbflow5.contentprovider.annotation.PathSegment -import com.dbflow5.contentprovider.annotation.TableEndpoint -import com.dbflow5.getContentValuesKey - -@ContentProvider(authority = TestContentProvider.AUTHORITY, - database = ContentDatabase::class) -object TestContentProvider2 { - @TableEndpoint(name = ContentProviderModel.ENDPOINT, - contentProvider = ContentDatabase::class) - object ContentProviderModel { - const val ENDPOINT = "ContentProviderModel" - - @JvmStatic - @ContentUri(path = TestContentProvider.ContentProviderModel.ENDPOINT, - type = ContentType.VND_MULTIPLE + TestContentProvider.ContentProviderModel.ENDPOINT) - var CONTENT_URI = TestContentProvider.buildUri(TestContentProvider.ContentProviderModel.ENDPOINT) - - @JvmStatic - @ContentUri(path = TestContentProvider.ContentProviderModel.ENDPOINT + "/#", - type = ContentType.VND_SINGLE + TestContentProvider.ContentProviderModel.ENDPOINT, - segments = arrayOf(PathSegment(segment = 1, column = "id"))) - fun withId(id: Long): Uri { - return TestContentProvider.buildUri(id.toString()) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.INSERT, paths = arrayOf(TestContentProvider.ContentProviderModel.ENDPOINT + "/#")) - fun onInsert(contentValues: ContentValues): Array { - val id = contentValues.getAsLong("id")!! - return arrayOf(withId(id)) - } - } -} - - -@ContentProvider(authority = TestContentProvider.AUTHORITY, database = ContentDatabase::class, - baseContentUri = TestContentProvider.BASE_CONTENT_URI) -object TestContentProvider { - - const val AUTHORITY = "com.dbflow5.test.provider" - - const val BASE_CONTENT_URI = "content://" - - fun buildUri(vararg paths: String): Uri { - val builder = Uri.parse(BASE_CONTENT_URI + AUTHORITY).buildUpon() - for (path in paths) { - builder.appendPath(path) - } - return builder.build() - } - - @TableEndpoint(name = ContentProviderModel.ENDPOINT, contentProvider = ContentDatabase::class) - object ContentProviderModel { - - const val ENDPOINT = "ContentProviderModel" - - @JvmStatic - @ContentUri(path = ENDPOINT, - type = ContentType.VND_MULTIPLE + ENDPOINT) - var CONTENT_URI = buildUri(ENDPOINT) - - @JvmStatic - @ContentUri(path = "$ENDPOINT/#", - type = ContentType.VND_SINGLE + ENDPOINT, - segments = arrayOf(PathSegment(segment = 1, column = "id"))) - fun withId(id: Long): Uri { - return buildUri(id.toString()) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.INSERT, paths = ["$ENDPOINT/#"]) - fun onInsert(contentValues: ContentValues): Array { - val id = contentValues.getAsLong("id")!! - return arrayOf(withId(id)) - } - - } - - @TableEndpoint(name = NoteModel.ENDPOINT, contentProvider = ContentDatabase::class) - object NoteModel { - - const val ENDPOINT = "NoteModel" - - @ContentUri(path = ENDPOINT, type = ContentType.VND_MULTIPLE + ENDPOINT) - var CONTENT_URI = buildUri(ENDPOINT) - - @JvmStatic - @ContentUri(path = "$ENDPOINT/#", type = ContentType.VND_MULTIPLE + ENDPOINT, - segments = arrayOf(PathSegment(column = "id", segment = 1))) - fun withId(id: Long): Uri { - return buildUri(ENDPOINT, id.toString()) - } - - @JvmStatic - @ContentUri(path = "$ENDPOINT/#/#", - type = ContentType.VND_SINGLE + ContentProviderModel.ENDPOINT, - segments = arrayOf(PathSegment(column = "id", segment = 2))) - fun fromList(id: Long): Uri { - return buildUri(ENDPOINT, "fromList", id.toString()) - } - - @JvmStatic - @ContentUri(path = "$ENDPOINT/#/#", - type = ContentType.VND_SINGLE + ContentProviderModel.ENDPOINT, - segments = arrayOf(PathSegment(column = "id", segment = 1), - PathSegment(column = "isOpen", segment = 2))) - fun withOpenId(id: Long, isOpen: Boolean): Uri { - return buildUri(ENDPOINT, id.toString(), isOpen.toString()) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.INSERT, paths = [ENDPOINT]) - fun onInsert(contentValues: ContentValues): Array { - val listId = contentValues.getAsLong(getContentValuesKey(contentValues, "providerModel"))!! - return arrayOf(ContentProviderModel.withId(listId), fromList(listId)) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.INSERT, paths = [ENDPOINT]) - fun onInsert2(contentValues: ContentValues): Uri { - val listId = contentValues.getAsLong(getContentValuesKey(contentValues, "providerModel"))!! - return fromList(listId) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.UPDATE, paths = ["$ENDPOINT/#"]) - fun onUpdate(context: Context, uri: Uri): Array { - val noteId = java.lang.Long.valueOf(uri.pathSegments[1]) - val c = context.contentResolver.query(uri, arrayOf("noteModel"), null, null, null) - c!!.moveToFirst() - val listId = c.getLong(c.getColumnIndex("providerModel")) - c.close() - - return arrayOf(withId(noteId), fromList(listId), ContentProviderModel.withId(listId)) - } - - @JvmStatic - @Notify(notifyMethod = NotifyMethod.DELETE, paths = ["$ENDPOINT/#"]) - fun onDelete(context: Context, uri: Uri): Array { - val noteId = java.lang.Long.valueOf(uri.pathSegments[1]) - val c = context.contentResolver.query(uri, null, null, null, null) - c!!.moveToFirst() - val listId = c.getLong(c.getColumnIndex("providerModel")) - c.close() - - return arrayOf(withId(noteId), fromList(listId), ContentProviderModel.withId(listId)) - } - } - - @TableEndpoint(name = TestSyncableModel.ENDPOINT, contentProvider = ContentDatabase::class) - object TestSyncableModel { - - const val ENDPOINT = "TestSyncableModel" - - @ContentUri(path = ENDPOINT, type = "${ContentType.VND_MULTIPLE}${ENDPOINT}") - var CONTENT_URI = buildUri(ENDPOINT) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/query/cache/ModelLruCacheTest.kt b/tests/src/androidTest/java/com/dbflow5/query/cache/ModelLruCacheTest.kt deleted file mode 100644 index 20f09e05de0b4c943605f7797322a928873d6489..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/query/cache/ModelLruCacheTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.dbflow5.query.cache - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.NumberModel -import org.junit.Assert -import org.junit.Test - -class ModelLruCacheTest : BaseUnitTest() { - - - @Test - fun validateCacheAddRemove() { - val cache = SimpleMapCache(10) - cache.addModel(1, NumberModel(1)) - - Assert.assertEquals(1, cache[1]!!.id) - Assert.assertEquals(1, cache.cache.size) - - cache.removeModel(1) - - Assert.assertTrue(cache.cache.isEmpty()) - } - - @Test - fun validateCacheClear() { - val cache = SimpleMapCache(10) - cache.addModel(1, NumberModel(1)) - cache.addModel(2, NumberModel(2)) - Assert.assertEquals(2, cache.cache.size) - - cache.clear() - - Assert.assertTrue(cache.cache.isEmpty()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.kt b/tests/src/androidTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.kt deleted file mode 100644 index d3cd6b10636ad56d3554134b86253c26f9434758..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/query/cache/SimpleMapCacheTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.dbflow5.query.cache - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class SimpleMapCacheTest : BaseUnitTest() { - - @Test - fun validateCacheAddRemove() { - val cache = SimpleMapCache(10) - cache.addModel("1", SimpleModel("1")) - - assertEquals("1", cache["1"]!!.name) - assertEquals(1, cache.cache.size) - - cache.removeModel("1") - - assertTrue(cache.cache.isEmpty()) - } - - @Test - fun validateCacheClear() { - val cache = SimpleMapCache(10) - cache.addModel("1", SimpleModel("1")) - cache.addModel("2", SimpleModel("2")) - assertEquals(2, cache.cache.size) - - cache.clear() - - assertTrue(cache.cache.isEmpty()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.kt b/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.kt deleted file mode 100644 index d545b864de7bc7749e407b0cd963f102ab27c40e..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorIteratorTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.dbflow5.query.list - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.select -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * Description: - */ -class FlowCursorIteratorTest : BaseUnitTest() { - - - @Test - fun testCanIterateFullList() { - var count = 0 - databaseForTable().beginTransactionAsync { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - (select from SimpleModel::class).cursorList(db).iterator() - }.success { _, iterator -> - assertFalse(iterator.isClosed) - iterator.use { cursorIterator -> - cursorIterator.forEach { - assertEquals("$count", it.name) - count++ - } - } - assertTrue(iterator.isClosed) - }.execute() - } - - @Test - fun testCanIteratePartialList() { - databaseForTable().beginTransactionAsync { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - - (select from SimpleModel::class).cursorList(db) - .iterator(2, 7) - }.success { _, iterator -> - var count = 0 - iterator.forEach { - assertEquals("${count + 2}", it.name) - count++ - } - assertEquals(7, count) - }.execute() - } - - @Test - fun testCanSupplyBadMaximumValue() { - databaseForTable().beginTransactionAsync { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - - (select from SimpleModel::class).cursorList(db) - .iterator(2, Long.MAX_VALUE) - }.success { _, iterator -> - var count = 0 - iterator.forEach { - assertEquals("${count + 2}", it.name) - count++ - } - assertEquals(8, count) - }.execute() - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorListTest.kt b/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorListTest.kt deleted file mode 100644 index cbf021043ad560700e0ae571030ef14a56049d11..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/query/list/FlowCursorListTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.dbflow5.query.list - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.select -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: - */ -class FlowCursorListTest : BaseUnitTest() { - - @Test - fun validateCursorPassed() { - databaseForTable { db -> - val cursor = (select from SimpleModel::class).cursor(db) - val list = FlowCursorList.Builder(select from SimpleModel::class, db) - .cursor(cursor) - .build() - - assertEquals(cursor, list.cursor) - } - } - - @Test - fun validateModelQueriable() { - databaseForTable { db -> - val modelQueriable = (select from SimpleModel::class) - val list = FlowCursorList.Builder(modelQueriable, db) - .build() - - assertEquals(modelQueriable, list.modelQueriable) - } - } - - @Test - fun validateGetAll() { - databaseForTable { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - - val list = (select from SimpleModel::class).cursorList(db) - val all = list.all - assertEquals(list.count, all.size.toLong()) - } - } - - @Test - fun validateCursorChange() { - databaseForTable { db -> - (0..9).forEach { - SimpleModel("$it").save(db) - } - - val list = (select from SimpleModel::class).cursorList(db) - - var cursorListFound: FlowCursorList? = null - var count = 0 - val listener: (FlowCursorList) -> Unit = { loadedList -> - cursorListFound = loadedList - count++ - } - list.addOnCursorRefreshListener(listener) - assertEquals(10, list.count) - SimpleModel("10").save(db) - list.refresh() - assertEquals(11, list.count) - - assertEquals(list, cursorListFound) - - list.removeOnCursorRefreshListener(listener) - - list.refresh() - assertEquals(1, count) - } - } -} - diff --git a/tests/src/androidTest/java/com/dbflow5/runtime/DirectNotifierTest.kt b/tests/src/androidTest/java/com/dbflow5/runtime/DirectNotifierTest.kt deleted file mode 100644 index 301a61b48472d9d5375ed6923d5a4f7f9adc89c3..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/runtime/DirectNotifierTest.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.dbflow5.runtime - -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.dbflow5.ImmediateTransactionManager -import com.dbflow5.TestDatabase -import com.dbflow5.config.FlowManager -import com.dbflow5.config.databaseForTable -import com.dbflow5.database.AndroidSQLiteOpenHelper -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.delete -import com.dbflow5.query.insertInto -import com.dbflow5.query.set -import com.dbflow5.query.update -import com.dbflow5.structure.ChangeAction -import com.dbflow5.structure.delete -import com.dbflow5.structure.insert -import com.dbflow5.structure.save -import com.dbflow5.structure.update -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verify -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito - -@RunWith(AndroidJUnit4::class) -class DirectNotifierTest { - - val context: Context - get() = ApplicationProvider.getApplicationContext() - - @Before - fun setupTest() { - FlowManager.init(context) { - database({ - transactionManagerCreator(::ImmediateTransactionManager) - }, AndroidSQLiteOpenHelper.createHelperCreator(context)) - } - } - - @Test - fun validateCanNotifyDirect() { - databaseForTable { db -> - val simpleModel = SimpleModel("Name") - - val modelChange = mock>() - DirectModelNotifier.get().registerForModelStateChanges(SimpleModel::class.java, modelChange) - - simpleModel.insert(db) - verify(modelChange).onModelChanged(simpleModel, ChangeAction.INSERT) - - simpleModel.update(db) - verify(modelChange).onModelChanged(simpleModel, ChangeAction.UPDATE) - - simpleModel.save(db) - verify(modelChange).onModelChanged(simpleModel, ChangeAction.CHANGE) - - simpleModel.delete(db) - verify(modelChange).onModelChanged(simpleModel, ChangeAction.DELETE) - } - } - - @Test - fun validateCanNotifyWrapperClasses() { - databaseForTable { db -> - val modelChange = Mockito.mock(OnTableChangedListener::class.java) - DirectModelNotifier.get().registerForTableChanges(SimpleModel::class.java, modelChange) - - insertInto() - .columnValues(SimpleModel_Table.name to "name") - .executeInsert(db) - - verify(modelChange).onTableChanged(SimpleModel::class.java, ChangeAction.INSERT) - - (update() set SimpleModel_Table.name.eq("name2")) - .executeUpdateDelete(db) - - verify(modelChange).onTableChanged(SimpleModel::class.java, ChangeAction.UPDATE) - - delete().executeUpdateDelete(db) - - verify(modelChange).onTableChanged(SimpleModel::class.java, ChangeAction.DELETE) - } - } - - @After - fun teardown() { - FlowManager.destroy() - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/RXTestRule.kt b/tests/src/androidTest/java/com/dbflow5/rx2/RXTestRule.kt deleted file mode 100644 index 464f6c75541071f809e7ec8da2240b72b97c8684..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/RXTestRule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dbflow5.rx2 - -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import io.reactivex.rxjava3.schedulers.Schedulers -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement - -class RXTestRule : TestRule { - - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { - override fun evaluate() { - RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() } - RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } - } - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/TransactionObservablesTest.kt b/tests/src/androidTest/java/com/dbflow5/rx2/TransactionObservablesTest.kt deleted file mode 100644 index 34ccabe08c865ea6c9f2e77125bccac1442f39bd..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/TransactionObservablesTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.dbflow5.rx2 - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.database.DatabaseWrapper -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.select -import com.dbflow5.reactivestreams.transaction.asMaybe -import com.dbflow5.reactivestreams.transaction.asSingle -import com.dbflow5.structure.save -import org.junit.Assert.* -import org.junit.Test - -/** - * Description: - */ -class TransactionObservablesTest : BaseUnitTest() { - - @Test - fun testObservableRun() { - var successCalled = false - var list: List? = null - database() - .beginTransactionAsync { db: DatabaseWrapper -> - (0 until 10).forEach { - SimpleModel("$it").save(db) - } - } - .asSingle() - .doAfterSuccess { - database() - .beginTransactionAsync { db -> (select from SimpleModel::class).queryList(db) } - .asSingle() - .subscribe { loadedList: MutableList -> - list = loadedList - successCalled = true - } - }.subscribe() - - assertTrue(successCalled) - assertEquals(10, list!!.size) - } - - @Test - fun testMaybe() { - var simpleModel: SimpleModel? = SimpleModel() - database() - .beginTransactionAsync { (select from SimpleModel::class).querySingle(it) } - .asMaybe() - .subscribe { - simpleModel = it - } - assertNull(simpleModel) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.kt b/tests/src/androidTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.kt deleted file mode 100644 index a9eeeeea540ad5d72e34219931756636a8e502cb..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/query/CursorResultSubscriberTest.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.dbflow5.rx2.query - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.delete -import com.dbflow5.query.insert -import com.dbflow5.query.select -import com.dbflow5.reactivestreams.query.queryStreamResults -import com.dbflow5.reactivestreams.transaction.asFlowable -import com.dbflow5.structure.delete -import com.dbflow5.structure.insert -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Test - -class CursorResultSubscriberTest : BaseUnitTest() { - - @Test - fun testCanQueryStreamResults() { - databaseForTable { db -> - (0..9).forEach { SimpleModel("$it").save(db) } - - var count = 0 - (select from SimpleModel::class) - .queryStreamResults(db) - .subscribe { - count++ - assert(it != null) - } - - assertEquals(10, count) - } - } - - @Test - fun testCanObserveOnTableChangesWithModelOps() { - var count = 0 - (select from SimpleModel::class) - .asFlowable { db -> queryList(db) } - .subscribe { - count++ - } - val model = SimpleModel("test") - databaseForTable().executeTransaction { db -> - model.save(db) - model.delete(db) - model.insert(db) - } - assertEquals(2, count) // once for subscription, 1 for operations in transaction. - } - - @Test - fun testCanObserveOnTableChangesWithTableOps() { - databaseForTable { db -> - delete().executeUpdateDelete(db) - var count = 0 - var curList: MutableList = arrayListOf() - (select from SimpleModel::class) - .asFlowable { db -> queryList(db) } - .subscribe { - curList = it - count++ - } - db.executeTransaction { d -> - insert(SimpleModel::class, SimpleModel_Table.name) - .values("test") - .executeInsert(d) - insert(SimpleModel::class, SimpleModel_Table.name) - .values("test1") - .executeInsert(d) - insert(SimpleModel::class, SimpleModel_Table.name) - .values("test2") - .executeInsert(d) - } - - - assertEquals(3, curList.size) - - db.executeTransaction { d -> - val model = (select - from SimpleModel::class - where SimpleModel_Table.name.eq("test")).requireSingle(d) - model.delete(d) - } - db.tableObserver.checkForTableUpdates() - - assertEquals(2, curList.size) - assertEquals(3, count) // once for subscription, 2 for transactions - } - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/query/RXFlowableTest.kt b/tests/src/androidTest/java/com/dbflow5/rx2/query/RXFlowableTest.kt deleted file mode 100644 index f401849290efd61bb2c2b49919f4364bfcb00562..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/query/RXFlowableTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.dbflow5.rx2.query - -import com.dbflow5.BaseUnitTest -import com.dbflow5.TestDatabase -import com.dbflow5.config.database -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.Author -import com.dbflow5.models.Author_Table -import com.dbflow5.models.Blog -import com.dbflow5.models.Blog_Table -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.cast -import com.dbflow5.query.select -import com.dbflow5.reactivestreams.transaction.asFlowable -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Description: - */ -class RXFlowableTest : BaseUnitTest() { - - @Test - fun testCanObserveChanges() { - databaseForTable { db -> - (0..100).forEach { SimpleModel("$it").save(db) } - - var list = mutableListOf() - var triggerCount = 0 - val subscription = (select from SimpleModel::class - where cast(SimpleModel_Table.name).asInteger().greaterThan(50)) - .asFlowable { db -> queryList(db) } - .subscribe { - list = it - triggerCount += 1 - } - - assertEquals(50, list.size) - subscription.dispose() - - SimpleModel("should not trigger").save(db) - assertEquals(1, triggerCount) - } - - } - - @Test - fun testObservesJoinTables() { - database { db -> - val joinOn = Blog_Table.name.withTable() - .eq(Author_Table.first_name.withTable() + " " + Author_Table.last_name.withTable()) - assertEquals("`Blog`.`name`=`Author`.`first_name`+' '+`Author`.`last_name`", joinOn.query) - - var list = mutableListOf() - var calls = 0 - (select from Blog::class - leftOuterJoin Author::class - on joinOn) - .asFlowable { db -> queryList(db) } - .subscribe { - calls++ - list = it - } - - val authors = (1 until 11).map { Author(it, firstName = "${it}name", lastName = "${it}last") } - db.executeTransaction { d -> - (1 until 11).forEach { - Blog(it, name = "${it}name ${it}last", author = authors[it - 1]).save(d) - } - } - - assertEquals(10, list.size) - assertEquals(2, calls) // 1 for initial, 1 for batch of changes - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/query/RXQueryTests.kt b/tests/src/androidTest/java/com/dbflow5/rx2/query/RXQueryTests.kt deleted file mode 100644 index 94a25d86586c669d673de77df1c8dc9aa42b65a8..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/query/RXQueryTests.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.dbflow5.rx2.query - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.database.DatabaseStatement -import com.dbflow5.database.FlowCursor -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.query.insert -import com.dbflow5.query.property.Property -import com.dbflow5.query.select -import com.dbflow5.query.selectCountOf -import com.dbflow5.reactivestreams.transaction.asMaybe -import com.dbflow5.reactivestreams.transaction.asSingle -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Test - -class RXQueryTests : BaseUnitTest() { - - @Test - fun testCanQuery() { - databaseForTable { db -> - SimpleModel("Name").save(db) - - var cursor: FlowCursor? = null - - db.beginTransactionAsync { (select from SimpleModel::class).cursor(it) } - .asMaybe() - .subscribe { - cursor = it - } - - assertEquals(1, cursor!!.count) - cursor!!.close() - } - } - - @Test - fun testCanCompileStatement() { - var databaseStatement: DatabaseStatement? = null - databaseForTable { db -> - db.beginTransactionAsync { - insert(name.`is`("name")).compileStatement(it) - }.asSingle() - .subscribe { statement -> - databaseStatement = statement - } - databaseStatement!!.close() - } - } - - @Test - fun testCountMethod() { - databaseForTable { db -> - SimpleModel("name").save(db) - SimpleModel("name2").save(db) - var count = 0L - db.beginTransactionAsync { - (selectCountOf(Property.ALL_PROPERTY) from SimpleModel::class).longValue(it) - } - .asSingle() - .subscribe { value -> - count = value - } - - assertEquals(2, count) - } - } - - @Test - fun testInsertMethod() { - var count = 0L - databaseForTable() - .beginTransactionAsync { - (insert(name.eq("name"))).executeInsert(it) - }.asSingle() - .subscribe { c -> - count = c - } - - assertEquals(1, count) - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/rx2/query/RxModels.kt b/tests/src/androidTest/java/com/dbflow5/rx2/query/RxModels.kt deleted file mode 100644 index 22b63a3a0ec534fec0ae8f366016c26c849593a0..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/rx2/query/RxModels.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.dbflow5.rx2.query - -import com.dbflow5.TestDatabase -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.reactivestreams.structure.BaseRXModel - - -@Table(database = TestDatabase::class, allFields = true) -class SimpleRXModel(@PrimaryKey var id: String = "") : BaseRXModel() \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/CaseTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/CaseTest.kt deleted file mode 100644 index 2511aac17d782df70ec878f87b6aa44b1eceb444..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/CaseTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.case -import com.dbflow5.query.caseWhen -import com.dbflow5.query.property.propertyString -import org.junit.Assert.* -import org.junit.Test - -class CaseTest : BaseUnitTest() { - - @Test - fun simpleCaseTest() { - val case = case(propertyString("country")) - .whenever("USA") - .then("Domestic") - .`else`("Foreign") - assertEquals("CASE country WHEN 'USA' THEN 'Domestic' ELSE 'Foreign' END `Country`", - case.end("Country").query.trim()) - assertTrue(case.isEfficientCase) - } - - @Test - fun searchedCaseTest() { - val case = caseWhen(SimpleModel_Table.name.eq("USA")).then("Domestic") - .whenever(SimpleModel_Table.name.eq("CA")).then("Canada") - .`else`("Foreign") - assertEquals("CASE WHEN `name`='USA' THEN 'Domestic' WHEN `name`='CA' THEN 'Canada' ELSE 'Foreign'", - case.query.trim()) - assertFalse(case.isEfficientCase) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/DeleteTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/DeleteTest.kt deleted file mode 100644 index 523e605fa9d2ef30dc095eeac686ccba6f371064..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/DeleteTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.delete -import com.dbflow5.query.select -import com.dbflow5.structure.save -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Test - -class DeleteTest : BaseUnitTest() { - - @Test - fun validateQuery() { - assertEquals("DELETE ", delete().query) - } - - @Test - fun validateDeletion() { - databaseForTable { db -> - SimpleModel("name").save(db) - delete().execute(db) - assertFalse((select from SimpleModel::class).hasData(db)) - } - } - - @Test - fun validateDeletionWithQuery() { - databaseForTable { db -> - SimpleModel("name").save(db) - SimpleModel("another name").save(db) - - val where = delete().where(SimpleModel_Table.name.`is`("name")) - assertEquals("DELETE FROM `SimpleModel` WHERE `name`='name'", where.query.trim()) - where.execute(db) - - assertEquals(1, (select from SimpleModel::class).queryList(db).size) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.kt deleted file mode 100644 index 520386b9de17291b44ea7bb45f3e38f7d17c9ffa..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/ExistenceOperatorTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.ExistenceOperator -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - -class ExistenceOperatorTest : BaseUnitTest() { - - - @Test - fun validateQuery() { - assertEquals("EXISTS (SELECT * FROM `SimpleModel` WHERE `name`='name')", - ExistenceOperator( - (select from SimpleModel::class - where SimpleModel_Table.name.eq("name"))) - .query.trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/FromTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/FromTest.kt deleted file mode 100644 index 8c3e8308c8c048a0ade0dcc236b4283c86ebd9cc..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/FromTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class FromTest : BaseUnitTest() { - - @Test - fun validateSimpleFrom() { - assertEquals("SELECT * FROM `SimpleModel`", (select from SimpleModel::class).query.trim()) - } - - @Test - fun validateProjectionFrom() { - assertEquals("SELECT `name` FROM `SimpleModel`", (select(name) from SimpleModel::class).query.trim()) - } - - @Test - fun validateMultipleProjection() { - assertEquals("SELECT `name`,`name`,`id` FROM `SimpleModel`", - (select(name, TwoColumnModel_Table.name, id) from SimpleModel::class).query.trim()) - } - - @Test - fun validateAlias() { - assertEquals("SELECT * FROM `SimpleModel` AS `Simple`", (select from SimpleModel::class `as` "Simple").query.trim()) - } - - @Test - fun validateJoins() { - val from = (select from SimpleModel::class - innerJoin TwoColumnModel::class - on name.eq(TwoColumnModel_Table.name.withTable())) - assertEquals("SELECT * FROM `SimpleModel` INNER JOIN `TwoColumnModel` ON `name`=`TwoColumnModel`.`name`", - from.query.trim()) - assertTrue(from.associatedTables.isNotEmpty()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/IndexTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/IndexTest.kt deleted file mode 100644 index c42c392a5c0bf0cd2bf0a3dd2c43b7a3f402e717..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/IndexTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.indexOn -import com.dbflow5.query.nameAlias -import org.junit.Assert.assertEquals -import org.junit.Test - -class IndexTest : BaseUnitTest() { - - @Test - fun validateBasicIndex() { - assertEquals("CREATE INDEX IF NOT EXISTS `index` ON `SimpleModel`(`name`)", - indexOn("index", SimpleModel_Table.name).query) - } - - @Test - fun validateUniqueIndex() { - assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS `index` ON `SimpleModel`(`name`, `test`)", - indexOn("index").unique(true).and(SimpleModel_Table.name) - .and("test".nameAlias).query) - } - - @Test - fun validateBasicIndexNameAlias() { - assertEquals("CREATE INDEX IF NOT EXISTS `index` ON `SimpleModel`(`name`, `test`)", - indexOn("index", "name".nameAlias, "test".nameAlias).query) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/IndexedByTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/IndexedByTest.kt deleted file mode 100644 index fbe48d532677c579ad84c6ad662a8b562833343c..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/IndexedByTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.property.IndexProperty -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - -class IndexedByTest : BaseUnitTest() { - - @Test - fun validateQuery() { - val indexed = (select from SimpleModel::class) - .indexedBy(IndexProperty("Index", false, SimpleModel::class.java, SimpleModel_Table.name)) - assertEquals("SELECT * FROM `SimpleModel` INDEXED BY `Index`", indexed.query.trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/InsertTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/InsertTest.kt deleted file mode 100644 index d3ac0f980fcf5b2082bff250871144d5a16cd76a..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/InsertTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.dbflow5.sql.language - -import android.content.ContentValues -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.database.set -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.models.TwoColumnModel_Table.name -import com.dbflow5.query.NameAlias -import com.dbflow5.query.Operator -import com.dbflow5.query.OperatorGroup -import com.dbflow5.query.insertInto -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - -class InsertTest : BaseUnitTest() { - - @Test - fun validateInsert() { - assertEquals("INSERT INTO `SimpleModel` VALUES('something')", - insertInto().values("something").query.trim()) - } - - @Test - fun validateInsertOr() { - assertEquals("INSERT OR REPLACE INTO `SimpleModel` VALUES('something')", - insertInto().orReplace().values("something").query.trim()) - assertEquals("INSERT OR FAIL INTO `SimpleModel` VALUES('something')", - insertInto().orFail().values("something").query.trim()) - assertEquals("INSERT OR IGNORE INTO `SimpleModel` VALUES('something')", - insertInto().orIgnore().values("something").query.trim()) - assertEquals("INSERT OR REPLACE INTO `SimpleModel` VALUES('something')", - insertInto().orReplace().values("something").query.trim()) - assertEquals("INSERT OR ROLLBACK INTO `SimpleModel` VALUES('something')", - insertInto().orRollback().values("something").query.trim()) - assertEquals("INSERT OR ABORT INTO `SimpleModel` VALUES('something')", - insertInto().orAbort().values("something").query.trim()) - } - - @Test - fun validateQuestionIntention() { - "INSERT INTO `SimpleModel` VALUES('?')" - .assertEquals(insertInto().values("?")) - } - - @Test - fun validateInsertProjection() { - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 'id')", - insertInto().columns(name, id).values("name", "id").query.trim()) - } - - @Test - fun validateSelect() { - assertEquals("INSERT INTO `TwoColumnModel` SELECT * FROM `SimpleModel`", - insertInto().select(select from SimpleModel::class).query.trim()) - } - - @Test - fun validateColumns() { - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 'id')", - insertInto().asColumns().values("name", "id").query.trim()) - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 'id')", - insertInto().columns("name", "id").values("name", "id").query.trim()) - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 'id')", - insertInto().columns(listOf(name, id)).values("name", "id").query.trim()) - } - - @Test - fun validateColumnValues() { - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 0)", - insertInto().columnValues(name.eq("name"), id.eq(0)).query.trim()) - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 0)", - insertInto().columnValues(Operator.op(NameAlias.builder("name").build()).eq("name"), - id.eq(0)).query.trim()) - assertEquals("INSERT INTO `TwoColumnModel`(`name`, `id`) VALUES('name', 0)", - insertInto().columnValues(group = OperatorGroup.clause().andAll(name.eq("name"), id.eq(0))).query.trim()) - - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/JoinTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/JoinTest.kt deleted file mode 100644 index 95614f3178988a01ee11c1076ff6c27a0b10513b..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/JoinTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - - -class JoinTest : BaseUnitTest() { - - @Test - fun validateAliasJoin() { - assertEquals("SELECT * FROM `SimpleModel` INNER JOIN `TwoColumnModel` AS `Name` ON `TwoColumnModel`.`name`=`name`", - ((select from SimpleModel::class innerJoin - TwoColumnModel::class).`as`("Name") on TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name)).query.trim()) - } - - @Test - fun testInnerJoin() { - val join = select from SimpleModel::class innerJoin - TwoColumnModel::class on TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name) - assertEquals("SELECT * FROM `SimpleModel` INNER JOIN `TwoColumnModel` ON `TwoColumnModel`.`name`=`name`", - join.query.trim()) - } - - @Test - fun testLeftOuterJoin() { - val join = select from SimpleModel::class leftOuterJoin - TwoColumnModel::class on TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name) - assertEquals("SELECT * FROM `SimpleModel` LEFT OUTER JOIN `TwoColumnModel` ON `TwoColumnModel`.`name`=`name`", - join.query.trim()) - } - - @Test - fun testCrossJoin() { - val join = select from SimpleModel::class crossJoin - TwoColumnModel::class on TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name) - assertEquals("SELECT * FROM `SimpleModel` CROSS JOIN `TwoColumnModel` ON `TwoColumnModel`.`name`=`name`", - join.query.trim()) - } - - @Test - fun testMultiJoin() { - val join = select from SimpleModel::class innerJoin - TwoColumnModel::class on TwoColumnModel_Table.name.withTable().eq(SimpleModel_Table.name) crossJoin - TwoColumnModel::class on TwoColumnModel_Table.id.withTable().eq(SimpleModel_Table.name) - assertEquals("SELECT * FROM `SimpleModel` INNER JOIN `TwoColumnModel` ON `TwoColumnModel`.`name`=`name`" + - " CROSS JOIN `TwoColumnModel` ON `TwoColumnModel`.`id`=`name`", - join.query.trim()) - } - - @Test - fun testInnerJoinOnUsing() { - val join = select from SimpleModel::class innerJoin - TwoColumnModel::class using SimpleModel_Table.name.withTable() - assertEquals("SELECT * FROM `SimpleModel` INNER JOIN `TwoColumnModel` USING (`SimpleModel`.`name`)", - join.query.trim()) - } - - @Test - fun testNaturalJoin() { - val join = (select from SimpleModel::class naturalJoin - TwoColumnModel::class).end() - assertEquals("SELECT * FROM `SimpleModel` NATURAL JOIN `TwoColumnModel`", - join.query.trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/MethodTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/MethodTest.kt deleted file mode 100644 index 293ee4313702a20cf1acbb2a5288592361cea0bb..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/MethodTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.models.TwoColumnModel_Table.name -import com.dbflow5.query.avg -import com.dbflow5.query.cast -import com.dbflow5.query.count -import com.dbflow5.query.date -import com.dbflow5.query.datetime -import com.dbflow5.query.groupConcat -import com.dbflow5.query.ifNull -import com.dbflow5.query.max -import com.dbflow5.query.min -import com.dbflow5.query.nullIf -import com.dbflow5.query.random -import com.dbflow5.query.replace -import com.dbflow5.query.strftime -import com.dbflow5.query.sum -import com.dbflow5.query.total -import com.dbflow5.sql.SQLiteType -import org.junit.Assert.assertEquals -import org.junit.Test - -class MethodTest : BaseUnitTest() { - - @Test - fun testMainMethods() { - assertEquals("AVG(`name`, `id`)", avg(name, id).query) - assertEquals("COUNT(`name`, `id`)", count(name, id).query) - assertEquals("GROUP_CONCAT(`name`, `id`)", groupConcat(name, id).query) - assertEquals("MAX(`name`, `id`)", max(name, id).query) - assertEquals("MIN(`name`, `id`)", min(name, id).query) - assertEquals("SUM(`name`, `id`)", sum(name, id).query) - assertEquals("TOTAL(`name`, `id`)", total(name, id).query) - assertEquals("CAST(`name` AS INTEGER)", cast(name).`as`(SQLiteType.INTEGER).query) - assertEquals("REPLACE(`name`, 'Andrew', 'Grosner')", replace(name, "Andrew", "Grosner").query) - } - - @Test - fun test_strftime() { - assertEquals("strftime('%s', 'now')", strftime("%s", "now").query) - } - - @Test - fun test_dateMethod() { - assertEquals("date('now', 'start of month', '+1 month')", - date("now", "start of month", "+1 month").query) - } - - @Test - fun test_datetimeMethod() { - assertEquals("datetime(1092941466, 'unix epoch')", - datetime(1092941466, "unix epoch").query) - } - - @Test - fun testIfNull() { - assertEquals("IFNULL(`name`, `id`)", ifNull(name, id).query) - } - - @Test - fun testNulllIf() { - assertEquals("NULLIF(`name`, `id`)", nullIf(name, id).query) - } - - @Test - fun random_generates_correct_query() { - assertEquals("RANDOM()", random.query) - } - - @Test - fun testOpMethods() { - assertEquals("AVG(`name` + `id`)", avg(name + id).query) - assertEquals("AVG(`name` + `id`)", (avg(name) + id).query) - assertEquals("AVG(`name` - `id`)", avg(name - id).query) - assertEquals("AVG(`name` - `id`)", (avg(name) - id).query) - assertEquals("AVG(`name` / `id`)", avg(name / id).query) - assertEquals("AVG(`name` * `id`)", (avg(name) * id).query) - assertEquals("AVG(`name` % `id`)", avg(name % id).query) - assertEquals("AVG(`name` % `id`)", (avg(name) % id).query) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/NameAliasTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/NameAliasTest.kt deleted file mode 100644 index 4b679eb38c82ae914d25760789c691db8b8bfb77..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/NameAliasTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.query.NameAlias -import com.dbflow5.query.`as` -import com.dbflow5.query.nameAlias -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Test - -class NameAliasTest : BaseUnitTest() { - - @Test - fun testSimpleCase() { - assertEquals("`name`", "name".nameAlias.query) - } - - @Test - fun testAlias() { - assertEquals("`name` AS `alias`", "name".`as`("alias").fullQuery) - } - - @Test - fun validateBuilder() { - val nameAlias = NameAlias.builder("name") - .keyword("DISTINCT") - .`as`("Alias") - .withTable("MyTable") - .shouldAddIdentifierToAliasName(false) - .shouldAddIdentifierToName(false) - .shouldStripAliasName(false) - .shouldStripIdentifier(false).build() - assertEquals("DISTINCT", nameAlias.keyword) - assertEquals("Alias", nameAlias.aliasName()) - assertEquals("Alias", nameAlias.aliasNameRaw()) - assertEquals("`MyTable`", nameAlias.tableName) - assertFalse(nameAlias.shouldStripAliasName) - assertFalse(nameAlias.shouldStripIdentifier) - assertEquals("Alias", nameAlias.nameAsKey) - assertEquals("DISTINCT `MyTable`.name AS Alias", nameAlias.fullQuery) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorGroupTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorGroupTest.kt deleted file mode 100644 index 221f9d84bb083607775263ade8130839f9f5e4ac..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorGroupTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.models.TwoColumnModel_Table.name -import com.dbflow5.query.OperatorGroup -import com.dbflow5.query.and -import com.dbflow5.query.andAll -import com.dbflow5.query.or -import com.dbflow5.query.orAll -import org.junit.Test - -class OperatorGroupTest : BaseUnitTest() { - - - @Test - fun validateCommaSeparated() { - "(`name`='name', `id`=0)".assertEquals(OperatorGroup.clause().setAllCommaSeparated(true).andAll(name.eq("name"), id.eq(0))) - } - - @Test - fun validateParanthesis() { - "`name`='name'".assertEquals(OperatorGroup.nonGroupingClause(name.eq("name")).setUseParenthesis(false)) - } - - @Test - fun validateOr() { - "(`name`='name' OR `id`=0)".assertEquals(name.eq("name") or id.eq(0)) - } - - @Test - fun validateOrAll() { - "(`name`='name' OR `id`=0 OR `name`='test')".assertEquals(name.eq("name") orAll arrayListOf(id.eq(0), name.eq("test"))) - } - - @Test - - fun validateAnd() { - "(`name`='name' AND `id`=0)".assertEquals(name.eq("name") and id.eq(0)) - } - - @Test - fun validateAndAll() { - "(`name`='name' AND `id`=0 AND `name`='test')".assertEquals(name.eq("name") andAll arrayListOf(id.eq(0), name.eq("test"))) - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorTest.kt deleted file mode 100644 index a0216505df442bea0596441f76843810910040e8..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/OperatorTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.annotation.Collate -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.op -import com.dbflow5.query.select -import org.junit.Test - -class OperatorTest : BaseUnitTest() { - - @Test - fun testEquals() { - "`name`='name'".assertEquals("name".op().eq("name")) - "`name`='name'".assertEquals("name".op().`is`("name")) - } - - @Test - fun testNotEquals() { - "`name`!='name'".assertEquals("name".op().notEq("name")) - "`name`!='name'".assertEquals("name".op().isNot("name")) - } - - @Test - fun testLike() { - "`name` LIKE 'name'".assertEquals("name".op().like("name")) - "`name` NOT LIKE 'name'".assertEquals("name".op().notLike("name")) - "`name` GLOB 'name'".assertEquals("name".op().glob("name")) - } - - @Test - fun testMath() { - "`name`>'name'".assertEquals("name".op().greaterThan("name")) - "`name`>='name'".assertEquals("name".op().greaterThanOrEq("name")) - "`name`<'name'".assertEquals("name".op().lessThan("name")) - "`name`<='name'".assertEquals("name".op().lessThanOrEq("name")) - "`name`+'name'".assertEquals("name".op() + "name") - "`name`-'name'".assertEquals("name".op() - "name") - "`name`/'name'".assertEquals("name".op() / "name") - "`name`*'name'".assertEquals("name".op() * "name") - "`name`%'name'".assertEquals("name".op() % "name") - } - - @Test - fun testCollate() { - "`name` COLLATE NOCASE".assertEquals("name".op() collate Collate.NOCASE) - "`name` COLLATE NOCASE".assertEquals("name".op() collate "NOCASE") - } - - @Test - fun testBetween() { - "`id` BETWEEN 6 AND 7".assertEquals(id.between(6) and 7) - } - - @Test - fun testIn() { - "`id` IN (5,6,7,8,9)".assertEquals(id.`in`(5, 6, 7, 8) and 9) - "`id` NOT IN (SELECT * FROM `SimpleModel`)".assertEquals(id.notIn(select from SimpleModel::class)) - } - - @Test - fun matchOperator() { - "`name` MATCH 'age'".assertEquals("name".op() match "age") - } - -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/OrderByTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/OrderByTest.kt deleted file mode 100644 index d21abbfd1f3a64a75c53d902846933b4d6b144f6..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/OrderByTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.annotation.Collate -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.query.OrderBy -import com.dbflow5.query.nameAlias -import org.junit.Test - -class OrderByTest : BaseUnitTest() { - - - @Test - fun validateBasicOrderBy() { - "`name` ASC".assertEquals(OrderBy.fromProperty(name).ascending()) - } - - @Test - fun validateDescendingOrderBy() { - "`name` DESC".assertEquals(OrderBy.fromNameAlias("name".nameAlias).descending()) - } - - @Test - fun validateCollate() { - "`name` COLLATE RTRIM ASC".assertEquals(OrderBy.fromProperty(name).ascending() collate Collate.RTRIM) - } - - @Test - fun validateCustomOrdrBy() { - "`name` ASC This is custom".assertEquals(OrderBy.fromString("`name` ASC This is custom")) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/SelectTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/SelectTest.kt deleted file mode 100644 index cd6203e0c25a225530381ce0087e6afa5f9a3877..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/SelectTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.models.TwoColumnModel_Table.name -import com.dbflow5.query.select -import org.junit.Test - -class SelectTest : BaseUnitTest() { - - @Test - fun validateSelect() { - "SELECT `name`,`id` FROM `TwoColumnModel`".assertEquals(select(name, id) from TwoColumnModel::class) - } - - @Test - fun validateSelectDistinct() { - "SELECT DISTINCT `name` FROM `SimpleModel`".assertEquals(select(name).distinct() from SimpleModel::class) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/SetTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/SetTest.kt deleted file mode 100644 index 2dd2a20a549135a0797d6a5ba09fb061ce685291..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/SetTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.set -import com.dbflow5.query.update -import org.junit.Test - -class SetTest : BaseUnitTest() { - - @Test - fun validateSetWithConditions() { - "UPDATE `SimpleModel` SET `name`='name'".assertEquals(update() set name.`is`("name")) - } - - @Test - fun validateMultipleConditions() { - "UPDATE `SimpleModel` SET `name`='name', `id`=0".assertEquals(update() set name.eq("name") and id.eq(0)) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/TriggerTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/TriggerTest.kt deleted file mode 100644 index 965a11b899e4b2c011328b1887ae0edb6a62c501..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/TriggerTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.NameAlias -import com.dbflow5.query.cast -import com.dbflow5.query.createTempTrigger -import com.dbflow5.query.createTrigger -import com.dbflow5.query.insert -import com.dbflow5.query.insertOn -import com.dbflow5.query.property.property -import com.dbflow5.query.select -import com.dbflow5.query.updateOn -import com.dbflow5.sql.SQLiteType -import com.dbflow5.structure.insert -import org.junit.Assert.assertNotNull -import org.junit.Test - -class TriggerTest : BaseUnitTest() { - - @Test - fun validateBasicTrigger() { - ("CREATE TRIGGER IF NOT EXISTS `MyTrigger` AFTER INSERT ON `SimpleModel` " + - "\nBEGIN" + - "\nINSERT INTO `TwoColumnModel`(`name`) VALUES(`new`.`name`);" + - "\nEND").assertEquals(createTrigger("MyTrigger").after() insertOn SimpleModel::class begin - insert(TwoColumnModel::class).columnValues(name to NameAlias.ofTable("new", "name"))) - } - - @Test - fun validateUpdateTriggerMultiline() { - ("CREATE TEMP TRIGGER IF NOT EXISTS `MyTrigger` BEFORE UPDATE ON `SimpleModel` " + - "\nBEGIN" + - "\nINSERT INTO `TwoColumnModel`(`name`) VALUES(`new`.`name`);" + - "\nINSERT INTO `TwoColumnModel`(`id`) VALUES(CAST(`new`.`name` AS INTEGER));" + - "\nEND") - .assertEquals( - createTempTrigger("MyTrigger").before() - updateOn SimpleModel::class - begin - insert(TwoColumnModel::class, name).values(NameAlias.ofTable("new", "name")) - and - insert(TwoColumnModel::class, id) - .values(cast(NameAlias.ofTable("new", "name").property).`as`(SQLiteType.INTEGER)) - - ) - } - - @Test - fun validateTriggerWorks() { - databaseForTable { db -> - val trigger = createTrigger("MyTrigger").after() insertOn SimpleModel::class begin - insert(TwoColumnModel::class).columnValues(name to NameAlias.ofTable("new", "name")) - trigger.enable(db) - SimpleModel("Test").insert(db) - - val result = select from TwoColumnModel::class where (name eq "Test") - assertNotNull(result) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.kt deleted file mode 100644 index 2ed58571820d540b00eb8bc05e8902099175dba2..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/UnsafeStringOperatorTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.UnSafeStringOperator -import com.dbflow5.query.select -import org.junit.Test - -class UnsafeStringOperatorTest : BaseUnitTest() { - - @Test - fun testCanIncludeInQuery() { - val op = UnSafeStringOperator("name = ?, id = ?, test = ?", arrayOf("'name'", "0", "'test'")) - "name = 'name', id = 0, test = 'test'".assertEquals(op) - "SELECT * FROM `SimpleModel` WHERE name = 'name', id = 0, test = 'test'".assertEquals(select from SimpleModel::class where op) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/UpdateTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/UpdateTest.kt deleted file mode 100644 index 4e1841811e74aa8a5472c8695727e51f66498eb0..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/UpdateTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.annotation.ConflictAction -import com.dbflow5.assertEquals -import com.dbflow5.models.NumberModel -import com.dbflow5.models.NumberModel_Table.id -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.query.property.Property -import com.dbflow5.query.set -import com.dbflow5.query.update -import org.junit.Test - -class UpdateTest : BaseUnitTest() { - - @Test - fun validateUpdateRollback() { - "UPDATE OR ROLLBACK `SimpleModel`".assertEquals(update().orRollback()) - } - - @Test - fun validateUpdateFail() { - "UPDATE OR FAIL `SimpleModel`".assertEquals(update().orFail()) - } - - @Test - fun validateUpdateIgnore() { - "UPDATE OR IGNORE `SimpleModel`".assertEquals(update().orIgnore()) - } - - @Test - fun validateUpdateReplace() { - "UPDATE OR REPLACE `SimpleModel`".assertEquals(update().orReplace()) - } - - @Test - fun validateUpdateAbort() { - "UPDATE OR ABORT `SimpleModel`".assertEquals(update().orAbort()) - } - - @Test - fun validateSetQuery() { - "UPDATE `SimpleModel` SET `name`='name'".assertEquals(update() set (name eq "name")) - } - - @Test - fun validateWildcardQuery() { - "UPDATE OR FAIL `NumberModel` SET `id`=? WHERE `id`=?".assertEquals(update().or(ConflictAction.FAIL) - .set(id.eq(Property.WILDCARD)) - .where(id.eq(Property.WILDCARD))) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/WhereTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/WhereTest.kt deleted file mode 100644 index 7251054970afff259d520a09896fbab43633d0f3..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/WhereTest.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.dbflow5.sql.language - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table.name -import com.dbflow5.models.TwoColumnModel -import com.dbflow5.models.TwoColumnModel_Table.id -import com.dbflow5.query.NameAlias -import com.dbflow5.query.OrderBy.Companion.fromNameAlias -import com.dbflow5.query.Where -import com.dbflow5.query.groupBy -import com.dbflow5.query.having -import com.dbflow5.query.min -import com.dbflow5.query.nameAlias -import com.dbflow5.query.or -import com.dbflow5.query.property.property -import com.dbflow5.query.select -import com.dbflow5.query.update -import org.junit.Assert.assertTrue -import org.junit.Assert.fail -import org.junit.Test - -class WhereTest : BaseUnitTest() { - - @Test - fun validateBasicWhere() { - val query = select from SimpleModel::class where name.`is`("name") - "SELECT * FROM `SimpleModel` WHERE `name`='name'".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateComplexQueryWhere() { - val query = select from SimpleModel::class where name.`is`("name") or id.eq(1) and (id.`is`(0) or name.eq("hi")) - "SELECT * FROM `SimpleModel` WHERE `name`='name' OR `id`=1 AND (`id`=0 OR `name`='hi')".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateGroupBy() { - val query = select from SimpleModel::class where name.`is`("name") groupBy name - "SELECT * FROM `SimpleModel` WHERE `name`='name' GROUP BY `name`".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateGroupByNameAlias() { - val query = (select from SimpleModel::class where name.`is`("name")).groupBy("name".nameAlias, "id".nameAlias) - "SELECT * FROM `SimpleModel` WHERE `name`='name' GROUP BY `name`,`id`".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateGroupByNameProps() { - val query = (select from SimpleModel::class where name.`is`("name")).groupBy(name, id) - "SELECT * FROM `SimpleModel` WHERE `name`='name' GROUP BY `name`,`id`".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateHaving() { - val query = select from SimpleModel::class where name.`is`("name") having name.like("That") - "SELECT * FROM `SimpleModel` WHERE `name`='name' HAVING `name` LIKE 'That'".assertEquals(query) - assertCanCopyQuery(query) - - "SELECT * FROM `SimpleModel` GROUP BY exampleValue HAVING MIN(ROWID)>5".assertEquals( - (select from SimpleModel::class - groupBy NameAlias.rawBuilder("exampleValue").build() - having min(NameAlias.rawBuilder("ROWID").build().property).greaterThan(5)) - ) - } - - @Test - fun validateLimit() { - val query = select from SimpleModel::class where name.`is`("name") limit 10 - "SELECT * FROM `SimpleModel` WHERE `name`='name' LIMIT 10".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateOffset() { - val query = select from SimpleModel::class where name.`is`("name") offset 10 - "SELECT * FROM `SimpleModel` WHERE `name`='name' OFFSET 10".assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateWhereExists() { - val query = (select from SimpleModel::class - whereExists (select(name) from SimpleModel::class where name.like("Andrew"))) - ("SELECT * FROM `SimpleModel` " + - "WHERE EXISTS (SELECT `name` FROM `SimpleModel` WHERE `name` LIKE 'Andrew')").assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateOrderByWhere() { - val query = (select from SimpleModel::class - where name.eq("name")).orderBy(name, true) - ("SELECT * FROM `SimpleModel` WHERE `name`='name' ORDER BY `name` ASC").assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateOrderByWhereAlias() { - val query = (select from SimpleModel::class - where name.eq("name")).orderBy("name".nameAlias, true) - ("SELECT * FROM `SimpleModel` " + - "WHERE `name`='name' ORDER BY `name` ASC").assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateOrderBy() { - val query = (select from SimpleModel::class - where name.eq("name") orderBy fromNameAlias("name".nameAlias).ascending()) - ("SELECT * FROM `SimpleModel` " + - "WHERE `name`='name' ORDER BY `name` ASC").assertEquals(query) - assertCanCopyQuery(query) - } - - private fun assertCanCopyQuery(query: Where) { - val actual = query.cloneSelf() - query.assertEquals(actual) - assertTrue(actual !== query) - } - - @Test - fun validateOrderByAll() { - val query = (select from TwoColumnModel::class - where name.eq("name")) - .orderByAll(listOf( - fromNameAlias("name".nameAlias).ascending(), - fromNameAlias("id".nameAlias).descending())) - ("SELECT * FROM `TwoColumnModel` " + - "WHERE `name`='name' ORDER BY `name` ASC,`id` DESC").assertEquals(query) - assertCanCopyQuery(query) - } - - @Test - fun validateNonSelectThrowError() { - databaseForTable { db -> - try { - update().set(name.`is`("name")).querySingle(db) - fail("Non select passed") - } catch (i: IllegalArgumentException) { - // expected - } - - try { - update().set(name.`is`("name")).queryList(db) - fail("Non select passed") - } catch (i: IllegalArgumentException) { - // expected - } - } - } - - @Test - fun validate_match_operator() { - val query = (select from SimpleModel::class where (name match "%s")) - ("SELECT * FROM `SimpleModel` WHERE `name` MATCH '%s'").assertEquals(query) - assertCanCopyQuery(query) - } -} diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/BytePropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/BytePropertyTest.kt deleted file mode 100644 index 7aff63b264d57e7d8bb4c5c856d7b19ea0bb3c3e..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/BytePropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class BytePropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5", prop.`is`(5).query.trim()) - assertEquals("`Prop`=5", prop.eq(5).query.trim()) - assertEquals("`Prop`!=5", prop.notEq(5).query.trim()) - assertEquals("`Prop`!=5", prop.isNot(5).query.trim()) - assertEquals("`Prop`>5", prop.greaterThan(5).query.trim()) - assertEquals("`Prop`>=5", prop.greaterThanOrEq(5).query.trim()) - assertEquals("`Prop`<5", prop.lessThan(5).query.trim()) - assertEquals("`Prop`<=5", prop.lessThanOrEq(5).query.trim()) - assertEquals("`Prop` BETWEEN 5 AND 6", prop.between(5).and(6).query.trim()) - assertEquals("`Prop` IN (5,6,7,8)", prop.`in`(5, 6, 7, 8).query.trim()) - assertEquals("`Prop` NOT IN (5,6,7,8)", prop.notIn(5, 6, 7, 8).query.trim()) - assertEquals("`Prop`=`Prop` + 5", prop.concatenate(5).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/CharPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/CharPropertyTest.kt deleted file mode 100644 index f4dbf2a73f970e967d71c90605780d010455ca44..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/CharPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class CharPropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`='5'", prop.`is`('5').query.trim()) - assertEquals("`Prop`='5'", prop.eq('5').query.trim()) - assertEquals("`Prop`!='5'", prop.notEq('5').query.trim()) - assertEquals("`Prop`!='5'", prop.isNot('5').query.trim()) - assertEquals("`Prop`>'5'", prop.greaterThan('5').query.trim()) - assertEquals("`Prop`>='5'", prop.greaterThanOrEq('5').query.trim()) - assertEquals("`Prop`<'5'", prop.lessThan('5').query.trim()) - assertEquals("`Prop`<='5'", prop.lessThanOrEq('5').query.trim()) - assertEquals("`Prop` BETWEEN '5' AND '6'", prop.between('5').and('6').query.trim()) - assertEquals("`Prop` IN ('5','6','7','8')", prop.`in`('5', '6', '7', '8').query.trim()) - assertEquals("`Prop` NOT IN ('5','6','7','8')", prop.notIn('5', '6', '7', '8').query.trim()) - assertEquals("`Prop`=`Prop` || '5'", prop.concatenate('5').query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.kt deleted file mode 100644 index 3475b750e71367c714d0bd9c1fe6ccffec9b2247..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/DoublePropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class DoublePropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5.0", prop.`is`(5.0).query.trim()) - assertEquals("`Prop`=5.0", prop.eq(5.0).query.trim()) - assertEquals("`Prop`!=5.0", prop.notEq(5.0).query.trim()) - assertEquals("`Prop`!=5.0", prop.isNot(5.0).query.trim()) - assertEquals("`Prop`>5.0", prop.greaterThan(5.0).query.trim()) - assertEquals("`Prop`>=5.0", prop.greaterThanOrEq(5.0).query.trim()) - assertEquals("`Prop`<5.0", prop.lessThan(5.0).query.trim()) - assertEquals("`Prop`<=5.0", prop.lessThanOrEq(5.0).query.trim()) - assertEquals("`Prop` BETWEEN 5.0 AND 6.0", prop.between(5.0).and(6.0).query.trim()) - assertEquals("`Prop` IN (5.0,6.0,7.0,8.0)", prop.`in`(5.0, 6.0, 7.0, 8.0).query.trim()) - assertEquals("`Prop` NOT IN (5.0,6.0,7.0,8.0)", prop.notIn(5.0, 6.0, 7.0, 8.0).query.trim()) - assertEquals("`Prop`=`Prop` + 5.0", prop.concatenate(5.0).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.kt deleted file mode 100644 index 097274acca251c3b72ffb3818898957eb32931ad..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/FloatPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class FloatPropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5.0", prop.`is`(5f).query.trim()) - assertEquals("`Prop`=5.0", prop.eq(5f).query.trim()) - assertEquals("`Prop`!=5.0", prop.notEq(5f).query.trim()) - assertEquals("`Prop`!=5.0", prop.isNot(5f).query.trim()) - assertEquals("`Prop`>5.0", prop.greaterThan(5f).query.trim()) - assertEquals("`Prop`>=5.0", prop.greaterThanOrEq(5f).query.trim()) - assertEquals("`Prop`<5.0", prop.lessThan(5f).query.trim()) - assertEquals("`Prop`<=5.0", prop.lessThanOrEq(5f).query.trim()) - assertEquals("`Prop` BETWEEN 5.0 AND 6.0", prop.between(5f).and(6f).query.trim()) - assertEquals("`Prop` IN (5.0,6.0,7.0,8.0)", prop.`in`(5f, 6f, 7f, 8f).query.trim()) - assertEquals("`Prop` NOT IN (5.0,6.0,7.0,8.0)", prop.notIn(5f, 6f, 7f, 8f).query.trim()) - assertEquals("`Prop`=`Prop` + 5.0", prop.concatenate(5f).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.kt deleted file mode 100644 index 097d4a5c66d2ea43d79e8b240075bc29b1fc5002..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/IndexPropertyTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.config.databaseForTable -import com.dbflow5.models.SimpleModel -import com.dbflow5.models.SimpleModel_Table -import com.dbflow5.query.property.IndexProperty -import org.junit.Assert.assertEquals -import org.junit.Test - -class IndexPropertyTest : BaseUnitTest() { - - - @Test - fun validateIndexProperty() { - databaseForTable { db -> - val prop = IndexProperty("Index", true, SimpleModel::class.java, - SimpleModel_Table.name) - prop.createIfNotExists(db) - prop.drop(db) - assertEquals("`Index`", prop.indexName) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/IntPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/IntPropertyTest.kt deleted file mode 100644 index a5e4ba22e348029c373c8bd498a94aeed83fa8ca..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/IntPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class IntPropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5", prop.`is`(5).query.trim()) - assertEquals("`Prop`=5", prop.eq(5).query.trim()) - assertEquals("`Prop`!=5", prop.notEq(5).query.trim()) - assertEquals("`Prop`!=5", prop.isNot(5).query.trim()) - assertEquals("`Prop`>5", prop.greaterThan(5).query.trim()) - assertEquals("`Prop`>=5", prop.greaterThanOrEq(5).query.trim()) - assertEquals("`Prop`<5", prop.lessThan(5).query.trim()) - assertEquals("`Prop`<=5", prop.lessThanOrEq(5).query.trim()) - assertEquals("`Prop` BETWEEN 5 AND 6", prop.between(5).and(6).query.trim()) - assertEquals("`Prop` IN (5,6,7,8)", prop.`in`(5, 6, 7, 8).query.trim()) - assertEquals("`Prop` NOT IN (5,6,7,8)", prop.notIn(5, 6, 7, 8).query.trim()) - assertEquals("`Prop`=`Prop` + 5", prop.concatenate(5).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/LongPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/LongPropertyTest.kt deleted file mode 100644 index cdb69f24ceb6fdd9d562107e4162d1c40f58d85a..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/LongPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class LongPropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5", prop.`is`(5).query.trim()) - assertEquals("`Prop`=5", prop.eq(5).query.trim()) - assertEquals("`Prop`!=5", prop.notEq(5).query.trim()) - assertEquals("`Prop`!=5", prop.isNot(5).query.trim()) - assertEquals("`Prop`>5", prop.greaterThan(5).query.trim()) - assertEquals("`Prop`>=5", prop.greaterThanOrEq(5).query.trim()) - assertEquals("`Prop`<5", prop.lessThan(5).query.trim()) - assertEquals("`Prop`<=5", prop.lessThanOrEq(5).query.trim()) - assertEquals("`Prop` BETWEEN 5 AND 6", prop.between(5).and(6).query.trim()) - assertEquals("`Prop` IN (5,6,7,8)", prop.`in`(5, 6, 7, 8).query.trim()) - assertEquals("`Prop` NOT IN (5,6,7,8)", prop.notIn(5, 6, 7, 8).query.trim()) - assertEquals("`Prop`=`Prop` + 5", prop.concatenate(5).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.kt deleted file mode 100644 index b399ca40676fcc51d67c9ccabf0cabb1fc02c9a6..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyFactoryTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.property.property -import com.dbflow5.query.property.propertyString -import com.dbflow5.query.select -import org.junit.Assert.assertEquals -import org.junit.Test - -class PropertyFactoryTest : BaseUnitTest() { - - @Test - fun testPrimitives() { - assertEquals("'c'", 'c'.property.query) - assertEquals("5", 5.property.query) - assertEquals("5.0", 5.0.property.query) - assertEquals("5.0", 5.0f.property.query) - assertEquals("5", 5L.property.query) - assertEquals("5", 5.toShort().property.query) - assertEquals("5", 5.toByte().property.query) - val nullable: Any? = null - assertEquals("NULL", nullable.property.query) - assertEquals("(SELECT * FROM `SimpleModel`)", (select from SimpleModel::class).property.query) - assertEquals("SomethingCool", propertyString("SomethingCool").query) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyTest.kt deleted file mode 100644 index e06f91b57e1548fb1f7c17c26976b7479521cbda..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/PropertyTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.assertEquals -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class PropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - "`Prop`='5'".assertEquals(prop `is` "5") - "`Prop`='5'".assertEquals(prop eq "5") - "`Prop`!='5'".assertEquals(prop notEq "5") - "`Prop`!='5'".assertEquals(prop isNot "5") - "`Prop` LIKE '5'".assertEquals(prop like "5") - "`Prop` NOT LIKE '5'".assertEquals(prop notLike "5") - "`Prop` GLOB '5'".assertEquals(prop glob "5") - "`Prop`>'5'".assertEquals(prop greaterThan "5") - "`Prop`>='5'".assertEquals(prop greaterThanOrEq "5") - "`Prop`<'5'".assertEquals(prop lessThan "5") - "`Prop`<='5'".assertEquals(prop lessThanOrEq "5") - "`Prop` BETWEEN '5' AND '6'".assertEquals((prop between "5") and "6") - "`Prop` IN ('5','6','7','8')".assertEquals(prop.`in`("5", "6", "7", "8")) - "`Prop` NOT IN ('5','6','7','8')".assertEquals(prop.notIn("5", "6", "7", "8")) - "`Prop`=`Prop` || '5'".assertEquals(prop concatenate "5") - "`Prop` MATCH 'age'".assertEquals(prop match "age") - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.kt deleted file mode 100644 index 2e6a5d93c524413673dd19d9035f1846bc016599..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/ShortPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.Property -import org.junit.Assert.assertEquals -import org.junit.Test - -class ShortPropertyTest : BaseUnitTest() { - - @Test - fun testOperators() { - val prop = Property(SimpleModel::class.java, "Prop") - assertEquals("`Prop`=5", prop.`is`(5).query.trim()) - assertEquals("`Prop`=5", prop.eq(5).query.trim()) - assertEquals("`Prop`!=5", prop.notEq(5).query.trim()) - assertEquals("`Prop`!=5", prop.isNot(5).query.trim()) - assertEquals("`Prop`>5", prop.greaterThan(5).query.trim()) - assertEquals("`Prop`>=5", prop.greaterThanOrEq(5).query.trim()) - assertEquals("`Prop`<5", prop.lessThan(5).query.trim()) - assertEquals("`Prop`<=5", prop.lessThanOrEq(5).query.trim()) - assertEquals("`Prop` BETWEEN 5 AND 6", prop.between(5).and(6).query.trim()) - assertEquals("`Prop` IN (5,6,7,8)", prop.`in`(5, 6, 7, 8).query.trim()) - assertEquals("`Prop` NOT IN (5,6,7,8)", prop.notIn(5, 6, 7, 8).query.trim()) - assertEquals("`Prop`=`Prop` + 5", prop.concatenate(5).query.trim()) - } - - @Test - fun testAlias() { - val prop = Property(SimpleModel::class.java, "Prop", "Alias") - assertEquals("`Prop` AS `Alias`", prop.toString().trim()) - - val prop2 = Property(SimpleModel::class.java, - NameAlias.builder("Prop") - .shouldAddIdentifierToName(false) - .`as`("Alias") - .shouldAddIdentifierToAliasName(false) - .build()) - assertEquals("Prop AS Alias", prop2.toString().trim()) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.kt b/tests/src/androidTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.kt deleted file mode 100644 index 56ee640633fc87a6ff8e8e65f91addae33963e85..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sql/language/property/TypeConvertedPropertyTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dbflow5.sql.language.property - -import com.dbflow5.BaseUnitTest -import com.dbflow5.converter.DateConverter -import com.dbflow5.converter.TypeConverter -import com.dbflow5.models.Difficulty -import com.dbflow5.models.EnumTypeConverterModel_Table -import com.dbflow5.models.SimpleModel -import com.dbflow5.query.NameAlias -import com.dbflow5.query.property.TypeConvertedProperty -import org.junit.Assert.assertEquals -import org.junit.Test -import java.util.* - -class TypeConvertedPropertyTest : BaseUnitTest() { - - - @Test - fun testTypeConverter() { - val property = TypeConvertedProperty(SimpleModel::class.java, "Prop", true, - object : TypeConvertedProperty.TypeConverterGetter { - override fun getTypeConverter(modelClass: Class<*>): TypeConverter<*, *> = DateConverter() - }) - assertEquals("`Prop`", property.toString()) - - val date = Date() - assertEquals("`Prop`=${date.time}", property.eq(date).query) - - assertEquals("`SimpleModel`.`Prop`=${date.time}", property.withTable().eq(date).query) - - val inverted = property.invertProperty() - assertEquals("`Prop`=5050505", inverted.eq(5050505).query) - } - - @Test - fun testCustomEnumTypeConverter() { - - assertEquals("`difficulty`='H'", EnumTypeConverterModel_Table.difficulty.eq(Difficulty.HARD).query) - assertEquals("`EnumTypeConverterModel`.`difficulty`='H'", EnumTypeConverterModel_Table.difficulty.withTable().eq(Difficulty.HARD).query) - assertEquals("`et`.`difficulty`='H'", EnumTypeConverterModel_Table.difficulty.withTable(NameAlias.tableNameBuilder("et").build()).eq(Difficulty.HARD).query) - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherDatabase.kt b/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherDatabase.kt deleted file mode 100644 index fe1712e86e9dc4c81ec87180f428676276670109..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherDatabase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.dbflow5.sqlcipher - -import com.dbflow5.annotation.Database -import com.dbflow5.config.DBFlowDatabase - -@Database(version = 1) -abstract class CipherDatabase : DBFlowDatabase() \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTest.kt b/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTest.kt deleted file mode 100644 index bba4520a6ccdf3bdc2f7475525818234c61c7d22..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.dbflow5.sqlcipher - -import com.dbflow5.DBFlowInstrumentedTestRule -import com.dbflow5.DemoApp -import com.dbflow5.config.database -import com.dbflow5.query.delete -import com.dbflow5.query.select -import org.junit.Assert.assertTrue -import org.junit.Rule -import org.junit.Test - -/** - * Description: Ensures we can use SQLCipher - */ -class CipherTest { - - @JvmField - @Rule - var dblflowTestRule = DBFlowInstrumentedTestRule.create { - database(openHelperCreator = SQLCipherOpenHelper.createHelperCreator(DemoApp.context, "dbflow-rules")) - } - - @Test - fun testCipherModel() { - database { db -> - (delete() from CipherModel::class).execute(db) - val model = CipherModel(name = "name") - model.save(db) - assertTrue(model.exists(db)) - - val retrieval = (select from CipherModel::class - where CipherModel_Table.name.eq("name")) - .querySingle(db) - assertTrue(retrieval!!.id == model.id) - (delete() from CipherModel::class).execute(db) - } - } -} \ No newline at end of file diff --git a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTestObjects.kt b/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTestObjects.kt deleted file mode 100644 index 704b034a5ebda85733af828ac3945f0efc8dffdb..0000000000000000000000000000000000000000 --- a/tests/src/androidTest/java/com/dbflow5/sqlcipher/CipherTestObjects.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.dbflow5.sqlcipher - -import android.content.Context -import com.dbflow5.annotation.Column -import com.dbflow5.annotation.PrimaryKey -import com.dbflow5.annotation.Table -import com.dbflow5.config.DBFlowDatabase -import com.dbflow5.database.DatabaseCallback -import com.dbflow5.structure.BaseModel - -class SQLCipherOpenHelperImpl(context: Context, - databaseDefinition: DBFlowDatabase, - callback: DatabaseCallback?) - : SQLCipherOpenHelper(context, databaseDefinition, callback) { - override var cipherSecret = "dbflow-rules" -} - -@Table(database = CipherDatabase::class) -class CipherModel(@PrimaryKey(autoincrement = true) var id: Long = 0, - @Column var name: String? = null) : BaseModel() \ No newline at end of file diff --git a/tests/src/main/AndroidManifest.xml b/tests/src/main/AndroidManifest.xml deleted file mode 100644 index 2de105f2c011f95d7a689890285ee17835af6435..0000000000000000000000000000000000000000 --- a/tests/src/main/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/tests/src/main/assets/prepackaged.db b/tests/src/main/assets/prepackaged.db deleted file mode 100644 index f2d1c851ba961527665bda3e1ff327fc05635a5f..0000000000000000000000000000000000000000 Binary files a/tests/src/main/assets/prepackaged.db and /dev/null differ diff --git a/tests/src/main/assets/prepackaged_2.db b/tests/src/main/assets/prepackaged_2.db deleted file mode 100644 index f2d1c851ba961527665bda3e1ff327fc05635a5f..0000000000000000000000000000000000000000 Binary files a/tests/src/main/assets/prepackaged_2.db and /dev/null differ diff --git a/tests/src/main/java/com/dbflow5/DemoApp.kt b/tests/src/main/java/com/dbflow5/DemoApp.kt deleted file mode 100644 index 72a768215177a03a55c867731e9c65b695fc17c2..0000000000000000000000000000000000000000 --- a/tests/src/main/java/com/dbflow5/DemoApp.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.dbflow5 - -import android.annotation.SuppressLint -import android.app.Application -import android.content.Context - -class DemoApp : Application() { - - companion object { - @SuppressLint("StaticFieldLeak") - lateinit var context: Context - } - - override fun onCreate() { - super.onCreate() - context = this - } -} \ No newline at end of file diff --git a/tests/src/main/java/com/dbflow5/StubContentProvider.kt b/tests/src/main/java/com/dbflow5/StubContentProvider.kt deleted file mode 100644 index 170837eae3cf1172d647eb5ed8adb76bb746fac7..0000000000000000000000000000000000000000 --- a/tests/src/main/java/com/dbflow5/StubContentProvider.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.dbflow5 - -import android.content.ContentProvider -import android.content.ContentValues -import android.database.Cursor -import android.net.Uri - -/** - * Description: Used as a stub, include this in order to work around Android O changes to [ContentProvider] - */ -open class StubContentProvider : ContentProvider() { - - override fun insert(uri: Uri?, values: ContentValues?): Uri { - TODO("not implemented") - } - - override fun query(uri: Uri?, projection: Array?, selection: String?, - selectionArgs: Array?, sortOrder: String?): Cursor { - TODO("not implemented") - } - - override fun onCreate(): Boolean = true - - override fun update(uri: Uri?, values: ContentValues?, selection: String?, - selectionArgs: Array?): Int { - TODO("not implemented") - } - - override fun delete(uri: Uri?, selection: String?, selectionArgs: Array?): Int { - TODO("not implemented") - } - - override fun getType(uri: Uri?): String { - TODO("not implemented") - } -} \ No newline at end of file diff --git a/tests/src/main/java/com/dbflow5/test/DemoActivity.java b/tests/src/main/java/com/dbflow5/test/DemoActivity.java deleted file mode 100644 index 902201869c98a4adc89683a3e8224867b317d45f..0000000000000000000000000000000000000000 --- a/tests/src/main/java/com/dbflow5/test/DemoActivity.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.dbflow5.test; - -import android.app.Activity; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - - -public class DemoActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_demo); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_demo, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - return true; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/tests/src/main/res/layout/activity_demo.xml b/tests/src/main/res/layout/activity_demo.xml deleted file mode 100644 index 313157d1343c507f0dcea83d161f81d565ea1f86..0000000000000000000000000000000000000000 --- a/tests/src/main/res/layout/activity_demo.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/tests/src/main/res/menu/menu_demo.xml b/tests/src/main/res/menu/menu_demo.xml deleted file mode 100644 index 5277c875aa0fa7fc9507ae7e78fd8b44bf8d2f2b..0000000000000000000000000000000000000000 --- a/tests/src/main/res/menu/menu_demo.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/tests/src/main/res/values-w820dp/dimens.xml b/tests/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816444614bd64f68a372d1f93211628ee51d..0000000000000000000000000000000000000000 --- a/tests/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/tests/src/main/res/values/dimens.xml b/tests/src/main/res/values/dimens.xml deleted file mode 100644 index 47c82246738c4d056e8030d3a259206f42e8e15d..0000000000000000000000000000000000000000 --- a/tests/src/main/res/values/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 16dp - 16dp - diff --git a/tests/src/main/res/values/strings.xml b/tests/src/main/res/values/strings.xml deleted file mode 100644 index 817527a060f57e3f9761de13f74e6400a0cc1a0b..0000000000000000000000000000000000000000 --- a/tests/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Hello world! - Settings - - diff --git a/tests/src/test/AndroidManifest.xml b/tests/src/test/AndroidManifest.xml deleted file mode 100644 index 5d8853515e8c51ce91a7ea2ff208728739fad81c..0000000000000000000000000000000000000000 --- a/tests/src/test/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/tests/src/test/assets/migrations/Migrations/0.sql b/tests/src/test/assets/migrations/Migrations/0.sql deleted file mode 100644 index 884336367c2ae646232e7fea4b6f6c3682722d20..0000000000000000000000000000000000000000 --- a/tests/src/test/assets/migrations/Migrations/0.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE MigrationModel - ADD COLUMN addedColumn; - --- This is a test \ No newline at end of file diff --git a/tests/src/test/assets/testdb.db b/tests/src/test/assets/testdb.db deleted file mode 100644 index f2d1c851ba961527665bda3e1ff327fc05635a5f..0000000000000000000000000000000000000000 Binary files a/tests/src/test/assets/testdb.db and /dev/null differ diff --git a/tests/src/test/resources/com/android/tools/test_config.properties b/tests/src/test/resources/com/android/tools/test_config.properties deleted file mode 100644 index b26a774fdc73be9d5b29b45e4f7bf8fdf8be2d24..0000000000000000000000000000000000000000 --- a/tests/src/test/resources/com/android/tools/test_config.properties +++ /dev/null @@ -1,3 +0,0 @@ -android_merged_manifest=./build/intermediates/manifests/full/debug/AndroidManifest.xml -android_merged_resources=./build/intermediates/res/merged/debug -android_merged_assets=./build/intermediates/assets/debug \ No newline at end of file diff --git a/usage2/.gitignore b/usage2/.gitignore deleted file mode 100644 index 1a366fb0be2a438dddc9ea2e444bf91df11433a8..0000000000000000000000000000000000000000 --- a/usage2/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Node rules: -## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -## Dependency directory -## Commenting this out is preferred by some people, see -## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git -node_modules - -# Book build output -_book - -# eBook build output -*.epub -*.mobi -*.pdf \ No newline at end of file diff --git a/usage2/5.0-migration-guide.md b/usage2/5.0-migration-guide.md deleted file mode 100644 index d1f3172920c9d0c2b5dae8f080e79d594f3fad9d..0000000000000000000000000000000000000000 --- a/usage2/5.0-migration-guide.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -description: This lists out the major changes since < 5.0 ---- - -# 5.0 Migration Guide - -**Major Changes** - -1. Package name is now `com.dbflow5` , which enables bundling multiple versions in the same repo. - 1. **Note:** Using multiple versions of DBFlow on the same DB is not recommended, as it can lead to inconsistent usage on the database in respect to transaction queuing, migrations, or synchronization. -2. Library is now **100%** Kotlin, except generated code. KSP \(Kotlin Source Processing\) is promising, but will require major updates to support it directly. This means that any `-kotlinextensions` artifacts are not rolled within the library. -3. Library now adds support for **incremental annotation processing.** Please report any issues! -4. New artifacts: `paging`\(architecture components Paging\), `coroutines`, `contentprovider`, \(splits out `ContentProvider` usage out of main library\), `livedata`, `reactive-streams` \(RXJava3\). - 1. `RXJava`1 and 2 support is dropped given, [https://github.com/ReactiveX/RxJava/tree/2.x](https://github.com/ReactiveX/RxJava/tree/2.x) is now in maintenance mode. You simply need to copy paste the `reactive-streams` files you need and then replace the package names back to their RXJava2 equivalent. RXJava 1 is not quite equivalent and not supported. -5. `save()` methods on the `ModelAdapter` classes now use a more efficient `INSERT OR REPLACE` method, rather than check if `exists` manually from the DB before inserting or replacing. -6. `@Database` classes must now be an abstract class that extends `DBFlowDatabase` \(or related subclass\) - 1. ```text - @Database(version = 1) - abstract class AppDatabase : DBFlowDatabase() - ``` -7. Removed deprecated `@Database` annotation fields including `name`, `databaseExtension`, and `inMemory`. Use the `DatabaseConfig.Builder` object when initializing DBFlow. -8. The implicit `DatabaseWrapper` that was used in model operations is now required explicit. -9. ```text - // 4.x - // this would grab the default database from the FlowManager - model.save() - - // 5.x - database { db -> model.save(db) } - ``` - -10. `ModelAdapter.bindToContentValues` and corresponding `ContentValues` generated code is no l longer enabled by default. If you need the methods, set `@Table(generateContentValues = true)`. - 1. For `@ContentProvider` object, your db must now extend `ContentProviderDatabase` to supply `ContentValues` methods on the database. -11. Explicitly marking every field in a `@Table` with `@Column` is no longer the default. By default any field in the model class are referenced. To enable the old behavior use `@Table(allFields = false)` -12. Adds `@Fts3` and `@Fts4` annotations. See SQLite docs on [Fts3 and 4.](https://www.sqlite.org/fts3.html) -13. `@ModelView`: gets `orderedCursorLookup`, `assignDefaultValuesFromCursor` , and `createWithDatabase` that were allowed on `@Table` classes. - 1. `@ModelViewQuery` can now be used on a `Property` -14. `@QueryModel`: gets `orderedCursorLookup`, and`assignDefaultValuesFromCursor` that were allowed on `@Table` classes. -15. `IMultiKeyCacheConverter` renamed to `MultiKeyCacheConverter` -16. Performing DB operations not in a transaction will throw a new warning in `FlowLog`: `Database Not Running in a Transaction. Performance may be impacted, observability will need manual updates via db.tableObserver.checkForTableUpdates()` -17. `QueryModelAdapter` is deprecated as `RetrievalAdapter` performs all functionality. -18. DBFlowDatabase can now specify a `JournalMode` to support write-ahead logging. **Note**: on higher end devices this will enable `WriteAheadLogging` by default. -19. New `TableObserver` class on a `DBFlowDatabase`. Inspired by Room, this sets up `Trigger` on table changes for observed tables to efficiently track which table changes. This is useful in recomputing queries for `LiveData`, `Flowable` , or `Paging` `DataSource`. Whenever a `Transaction` is run on the DB, upon completing it, we check for any table changes and dispatch that to the active queries that are observed. - 1. ```text - (select from MyTable::class ...) - .toLiveData(db) - .observe(owner) { r -> } - ``` -20. `AlterTableMigration` supports default values for a column when adding a column. -21. `Index.enable` renamed to `createIfNotExists`, `Index.disable` renamed to `drop`. -22. Reduce generated code in tables. - diff --git a/usage2/README.md b/usage2/README.md deleted file mode 100644 index 558aa185034e0700033b0a1f679fc8ada7a38a4b..0000000000000000000000000000000000000000 --- a/usage2/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Usage Docs - -DBFlow is a Kotlin SQLite library for Android that makes it ridiculously easy to interact and use databases. Built with Annotation Processing, code use within a DB is fast, efficient, and type-safe. It removes the tedious \(and tough-to-maintain\) database interaction code, while providing a very SQLite-like query syntax. - -Creating a database is as easy as a few lines of code: - -```kotlin -@Database(version = 1) -abstract class AppDatabase: DBFlowDatabase -``` - -The `@Database` annotation generates a `DatabaseDefinition` which now references your SQLite Database on disk in the file named "AppDatabase.db". You can reference it in code as: - -```java -val db = database(); - -// or -database { db -> - -} -``` - -To ensure generated code in DBFlow is found by the library, initialize the library in your `Application` class: - -```kotlin -class MyApp : Application { - - override fun onCreate() { - super.onCreate() - FlowManager.init(this) - } -} -``` - -By default, DBFlow generates the `GeneratedDatabaseHolder` class, which is instantiated once by reflection, only once in memory. - -Creating a table is also very simple: - -```kotlin -@Table(database = AppDatabase::class, name = "User2") -class User(@PrimaryKey var id: Int = 0, - var firstName: String? = null, - var lastName: String? = null, - var email: String? = null) -``` - -Then to create, read, update, and delete the model: - -```kotlin -// always utilize DB transactions when possible. -databaseForTable().executeTransaction { db -> - val user = User(id = UUID.randomUUID(), - name = "Andrew Grosner", - age = 27) - user.insert(db) - - user.name = "Not Andrew Grosner"; - user.update(db) - - user.delete(db) -} - -// find adult users synchronously -val users = (select from User::class - where (User_Table.age greaterThan 18)) - .queryList(database()) - -// or asynchronous retrieval (preferred) -(select from User::class where User_Table.age.greaterThan(18)) - .async(database()) { queryList(it) } - .execute( - success = { transaction, result -> - // use result here - }, - error = { transaction, error -> - // handle any errors - } - ) - -// use coroutines! or RX3 or LiveData -async { - val result = (delete() where SimpleModel_Table.name.eq("5")) - .awaitTransact(db) { executeUpdateDelete(database) } -} -``` - diff --git a/usage2/advanced-usage/README.md b/usage2/advanced-usage/README.md deleted file mode 100644 index a4ebff1fcb24aac79518f33f9341908b32190f66..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Advanced Usage - -This section details the more advanced usages of DBFlow. - diff --git a/usage2/advanced-usage/caching.md b/usage2/advanced-usage/caching.md deleted file mode 100644 index 947ecfa3385215657b5a44b1049e3803bee29af3..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/caching.md +++ /dev/null @@ -1,139 +0,0 @@ -# Caching - -DBFlow provides seamless caching mechanisms to speed up retrieval from the database to skip querying the DB directly. - -Caching is not enabled by default, but it is very easy to enable. - -## When to Use Caching - -1. Retrieve the near-same list of objects from the DB when data does not change frequently. -2. Need to load large, full objects from the DB repeatedly in different places in the app. - -## When Not To Use Caching - -Do not use caching when: - -1. You project a subset of columns from the DB. i.e. \(`select(name, age, firstName)`\) - -2. The data is expected to change frequently. Any operation on the DB that is not tied to model instances will invalidate our cache. - -3. You load data from `@OneToMany`, or have nested `@OneToMany` fields within inner objects. - -**Note**: DBFlow is fast and efficient. Caching may not be required at all, except in very particular use-cases. Do not abuse. You can call `disableCaching()` on a query to ensure it's a fresh dataset. - -## Clearing Caches - -Sometimes the data becomes out of sync, or you perform a vanilla SQLite query, which causes data to get out of sync from the cache. In those cases call: - -```kotlin -modelAdapter().cacheAdapter.clearCache() -``` - -## How Caching Works - -Caching under the hood is done by storing an instance of each `Model` returned from a query on a specific table into memory. - -1. Developer enables caching on Table A - -2. Query from Table A - -3. When receiving the `Cursor`, we read the primary key values from it and look them up from `ModelCache`. If the `Model` exists, return it from cache; otherwise create new instance, read in values, and store in cache. - -4. That instance remains in memory such on next query, we return that instance instead of recreating one from a `Cursor`. - -5. When we call `ModelAdapter.save()`, `insert()`, `update()`, or `delete()`, we update model in the cache so that on next retrieval, the model with proper values is returned. - -6. When SQLite wrapper operations are performed on tables with caching, caches are not modified. When doing such a call, please call `TableA_Table.cacheAdapter.clearCache()` - -### Supported Backing Objects - -Caching is supported under the hood for: - -1. `SparseArray` via `SparseArrayBasedCache` \(platform SparseArray\) - -2. `Map` via `SimpleMapCache` - -3. `LruCache` via `ModelLruCache` \(copy of `LruCache`, so dependency avoided\) - -4. Custom Caching classes that implement `ModelCache` - -Cache sizes are not supported for `SimpleMapCache`. This is because `Map` can hold arbitrary size of contents. - -### Enable Caching - -To enable caching on a single-primary key table, simply specify that it is enabled: - -```kotlin -@Table(database = AppDatabase.class, cachingEnabled = true) -class CacheableModel { - - @PrimaryKey(autoincrement = true) - var id: Long = 0L - - @Column - var name: String? = null -} -``` - -to use caching on a table that uses multiple primary keys, [see](caching.md#multiple-primary-key-caching). - -By default we use a `SimpleMapCache`, which loads `Model` into a `Map`. The key is either the primary key of the object or a combination of the two, but it should have an associated `HashCode` and `equals()` value. - -## Modifying Cache Objects - -Any time a field on these objects are modified, you _should_ immediately save those since we have a direct reference to the object from the cache. Otherwise, the DB and cache could get into an inconsistent state. - -```kotlin - val result = (select from MyModel::class where (...)).querySingle(db) - result.name = "Name" - modelAdapter().save(result, db) -``` - -## Disable Caching For Some Queries - -To disable caching on certain queries as you might want to project on only a few columns, rather than the full dataset. Just call `disableCaching()`: - -```kotlin - select(My_Table.column, My_Table.column2) - .from(My::class) - .disableCaching() - .queryList(db) -``` - -## Advanced - -### Specifying cache Size - -To specify cache size, set `@Table(cacheSize = {size})`. Please note that not all caches support sizing. It's up to each cache. - -### Custom Caches - -To specify a custom cache for a table, please define a `@JvmField` field: - -```kotlin -companion object { - - @JvmField @ModelCacheField - val modelCache = SimpleMapCache() -} -``` - -### Multiple Primary Key Caching - -This allows for tables that have multiple primary keys be used in caching. To use, add a `@MultiCacheField` `@JvmField` field. for example we have a `Coordinate` class: - -```kotlin -@Table(database = AppDatabase.class, cachingEnabled = true) -class Coordinate(@PrimaryKey latitude: Double = 0.0, - @PrimaryKey longitude: Double = 0.0) { - - companion object { - @JvmField - @MultiCacheField - val cacheConverter = MultiKeyCacheConverter { values -> "${values[0]},${values[1]}" } - } -} -``` - -In this case we use the `MultiKeyCacheConverter` class, which specifies a key type that the object returns. The `getCachingKey` method returns an ordered set of `@PrimaryKey` columns in declaration order. Also the value that is returned should have an `equals()` or `hashcode()` specified \(use a `data class`\) especially when used in the `SimpleMapCache`. - diff --git a/usage2/advanced-usage/indexing.md b/usage2/advanced-usage/indexing.md deleted file mode 100644 index 6aa19dcd02d40c4857d102428107e0821b7e067b..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/indexing.md +++ /dev/null @@ -1,72 +0,0 @@ -# Indexing - -In SQLite, an `Index` is a pointer to specific columns in a table that enable super-fast retrieval. - -**Note**: The database size can increase significantly, however if performance is more important, the tradeoff can be worth it. - -Indexes are defined using the `indexGroups()` property of the `@Table` annotation. These operate similar to how `UniqueGroup` work: - -1. specify an `@IndexGroup` , giving it a number and `name` . The `name` is used in the database directly to create an index. - -2. Add the `@Index` annotation to a `@Column` and assign the `indexGroups` to the `number` you specified in the annotation. - -3. Build and an `IndexProperty` gets generated. This allows super-easy access to the index so you can enable/disable it with ease. - -**Note**: `Index` are not explicitly enabled unless coupled with an `IndexMigration`. \([read here](../usage/migrations.md#index-migrations)\). - -You can define as many `@IndexGroup` you want within a `@Table` as long as one field references the group. Also individual `@Column` can belong to any number of groups: - -```kotlin -@Table(database = TestDatabase::class, - indexGroups = [ - IndexGroup(number = 1, name = "firstIndex"), - IndexGroup(number = 2, name = "secondIndex"), - IndexGroup(number = 3, name = "thirdIndex") - ]) -class IndexModel2 { - - @Index(indexGroups = {1, 2, 3}) - @PrimaryKey - var id: Int = 0 - - @Index(indexGroups = 1) - @Column - var firstName: String = "" - - @Index(indexGroups = 2) - @Column - var lastName: String = "" - - @Index(indexGroups = {1, 3}) - @Column - var createdDate: Date? = null - - @Index(indexGroups = {2, 3}) - @Column - var isPro: Boolean = false -} -``` - -By defining the index this way, we generate an `IndexProperty`, which makes it very easy to enable, disable, and use it within queries: - -```kotlin -IndexModel2_Table.firstIndex.createIfNotExists(database); - -(select from IndexModel2::class - indexedBy IndexModel2_Table.firstIndex - where ...) - -IndexModel2_Table.firstIndex.drop(database); // turn it off when no longer needed. -``` - -## SQLite Index Wrapper - -For flexibility, we also support the SQLite `Index` wrapper object, in which the `IndexProperty` uses underneath. - -```kotlin -val index = indexOn("MyIndex", SomeTable_Table.name, SomeTable_Table.othercolumn) -index.createIfNotExists(database) - -index.drop(database) -``` - diff --git a/usage2/advanced-usage/listbasedqueries.md b/usage2/advanced-usage/listbasedqueries.md deleted file mode 100644 index 31ef006674aa2b71fee46b13c75dbb3b760bec73..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/listbasedqueries.md +++ /dev/null @@ -1,44 +0,0 @@ -# List Queries - -Converting a whole list at one chunk can be memory intensive. This lazily creates models from a `Cursor` for you and you can operate on it s if it's a list. It a acts like a list can be used in a for-loop: - -```kotlin -(select from MyTable::class - where ...) // some conditions - .flowQueryList(database).use { list -> - // list is just backed by an active cursor. - } - -(select from MyTable::class - where ...) // some conditions - .cursorList().use { list -> - // ensure you close these when done, as they utilize active cursors :) - // can use the list like a regular List - for (model in list) { - - } - - list.forEach { printLn("$it") } - } -``` - -**Note**: It's preferred within a `RecyclerView` to use the `QueryDataSource` with the Paging library, as this use can potentially lock the UI thread during heavy db usage. - -**Note:** Ensure you close the `FlowQueryList` when you are done using. - -## FlowCursorList - -The `FlowCursorList` is simply a wrapper around a standard `Cursor`, giving it the ability to cache `Model`, load items at specific position with conversion, and refresh its contents easily. - -The `FlowCursorList` provides these methods: - -1. `list[index]` - loads item from `Cursor` at specified position -2. `refresh()` - re-queries the underlying `Cursor`. Use a `OnCursorRefreshListener` to get callbacks when this occurs. -3. `list.all` - converts it to a `List` of all items from the `Cursor`, no caching used. -4. `list.count` - returns count of `Cursor` or 0 if `Cursor` is `null` -5. `list.isEmpty` - returns if count == 0 - -## Flow Query List - -This class is a much more powerful version of the `FlowCursorList`. This class acts as `List` and can be used almost wherever a `List` is used. - diff --git a/usage2/advanced-usage/multiplemodules.md b/usage2/advanced-usage/multiplemodules.md deleted file mode 100644 index a1440811500d920a080e53225d75b0740502bace..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/multiplemodules.md +++ /dev/null @@ -1,43 +0,0 @@ -# Multiple Modules - -In apps that want to share DBFlow across multiple modules or when developing a library module that uses DBFlow, we have to provide a little extra configuration to properly ensure that all database classes are accounted for. - -It's directly related to the fact that annotation processors are isolated between projects and are not shared. - -In order to add support for multiple modules, in each and every library/subproject that uses a DBFlow instance, you must add an annotation processing argument to its `build.gradle`: - -Using KAPT: - -```java -kapt { - arguments { - arg("targetModuleName", "SomeUniqueModuleName") - } -} -``` - -or if you use Android/Java: - -```java -// inside android -> defaultConfig -javaCompileOptions { - annotationProcessorOptions { - arguments = ['library': 'true'] - } - } -``` - -By passing the targetModuleName, we append that to the `GeneratedDatabaseHolder` class name to create the `{targetModuleName}GeneratedDatabaseHolder` module. - -**Note**: Specifying this in code means you need to specify the module when initializing DBFlow: - -From previous sample code, we recommend initializing the specific module inside your library, to prevent developer error. **Note**: Multiple calls to `FlowManager` will not adversely affect DBFlow. If DBFlow is already initialized, we append the module to DBFlow if and only if it does not already exist. - -```kotlin -fun initialize(context: Context) { - FlowManager.init(FlowConfig.builder(context) - .addDatabaseHolder(SomeUniqueModuleNameGeneratedDatabaseHolder::class) - .build()) -} -``` - diff --git a/usage2/advanced-usage/querymodels.md b/usage2/advanced-usage/querymodels.md deleted file mode 100644 index fe953b8618384c69056a61fb92cad8933c5b8a66..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/querymodels.md +++ /dev/null @@ -1,70 +0,0 @@ -# QueryModels - -A `QueryModel` maps any kind of custom query to a type object. These types are virtual and do not get represented by any real DB construct such as a `Table` - -We use a different annotation, `@QueryModel`, to define it separately. These do not allow for modifications in the DB, rather act as a marshal agent out of the DB. - -## Define a QueryModel - -For this example, we have a list of employees that we want to gather the average salary for each position in each department from our company. - -We defined an `Employee` table: - -```kotlin -@Table(database = AppDatabase::class) -class EmployeeModel(@PrimaryKey var uid: String = "", - var salary: Long = 0L, - var name: String = "", - var title: String = "", - var department: String = "") -``` - -We need someway to retrieve the results of this query, since we want to avoid dealing with the `Cursor` directly. We can use a SQLite query with our existing models, but we have no way to map it currently to our tables, since the query returns new Columns that do not represent any existing table: - -```kotlin -(select(EmployeeModel_Table.department, - avg(EmployeeModel_Table.salary.as("average_salary")), - EmployeeModel_Table.title) - from EmployeeModel::class) - .groupBy(EmployeeModel_Table.department, EmployeeModel_Table.title) -``` - -So we must define a `QueryModel`, representing the results of the query: - -```kotlin -@QueryModel(database = AppDatabase::class) -class AverageSalary(var title: String = "", - var average_salary: Long = 0L, - var department: String = "") -``` - -And adjust our query to handle the new output: - -```kotlin -(select(EmployeeModel_Table.department, - avg(EmployeeModel_Table.salary.as("average_salary")), - EmployeeModel_Table.title) - from EmployeeModel::class) - .groupBy(EmployeeModel_Table.department, EmployeeModel_Table.title) - .async(database) { it.customList() } - .execute { transaction, list -> - // utilize list - } -``` - -## Query Model Support - -`QueryModel` are read-only. We can only retrieve from DB into a cursor. - -They support inheritance and visibility modifiers as defined by [Models](../usage/models.md). - -`QueryModel` **do not** support: - -1. `InheritedField`/`InheritedPrimaryKey` - -2. `@PrimaryKey`/`@ForeignKey` - -3. direct caching. Can cache queries using the `withModelCache` on a SQLite query, which is a read-only cache. - -4. changing `useBooleanGetterSetters` for private boolean fields. - diff --git a/usage2/advanced-usage/sqlciphersupport.md b/usage2/advanced-usage/sqlciphersupport.md deleted file mode 100644 index 244d2d710e8e6e7edfd6669bbf9d89b54f3cf980..0000000000000000000000000000000000000000 --- a/usage2/advanced-usage/sqlciphersupport.md +++ /dev/null @@ -1,45 +0,0 @@ -# SQLCipher - -As of 3.0.0-beta2+, DBFlow now supports [SQLCipher](https://www.zetetic.net/sqlcipher/) fairly easily. - -To add the library add the library to your `build.gradle` with same version you are using with the rest of the library. - -```groovy -dependencies { - implementation "com.dbflow5:sqlcipher:${version}" - implementation "net.zetetic:android-database-sqlcipher:${sqlcipher_version}" -} -``` - -You also need to add the Proguard rule: - -```text --keep class net.sqlcipher.** { *; } --dontwarn net.sqlcipher.** -``` - -Next, you need to subclass the provided `SQLCipherOpenHelper` \(taken from test files\): - -```kotlin -class SQLCipherOpenHelperImpl(context: Context, - databaseDefinition: DBFlowDatabase, - callback: DatabaseCallback?) - : SQLCipherOpenHelper(context, databaseDefinition, callback) { - override val cipherSecret get() = "dbflow-rules" -} -``` - -_Note:_ that the constructor with `DatabaseDefinition` and `DatabaseHelperListener` is required. - -Then in your application class when initializing DBFlow: - -```kotlin -FlowManager.init(FlowConfig.Builder(context) - .database( - DatabaseConfig.Builder(CipherDatabase::class) { db, callback -> SQLCipherHelperImpl(context, databaseDefinition, callback)) - .build()) - .build()) -``` - -And that's it. You're all set to start using SQLCipher! - diff --git a/usage2/book.json b/usage2/book.json deleted file mode 100644 index 77682128ea4dba233f8641f3de678587049209ce..0000000000000000000000000000000000000000 --- a/usage2/book.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "plugins": [ "versions" ], - "pluginsConfig": { - "versions": { - "type": "tags" - } - } -} \ No newline at end of file diff --git a/usage2/contentprovidergeneration.md b/usage2/contentprovidergeneration.md deleted file mode 100644 index 9fa77a7788383ab2791b3ae925dc12146813e46e..0000000000000000000000000000000000000000 --- a/usage2/contentprovidergeneration.md +++ /dev/null @@ -1,220 +0,0 @@ -# ContentProviderGeneration - -This library includes a very fast, easy way to use `ContentProvider`! Using annotations, you can generate `ContentProvider` with ease. - -## Getting Started - -This feature is largely based off of [schematic](https://github.com/SimonVT/schematic), while leveraging DBFlow's power. - -### Placeholder ContentProvider - -In order to define a `ContentProvider`, you must define it in a placeholder class: - -```kotlin -@ContentProvider(authority = TestContentProvider.AUTHORITY, - database = TestDatabase::class, - baseContentUri = TestContentProvider.BASE_CONTENT_URI) -object TestContentProvider { - - const val AUTHORITY = "com.dbflow5.test.provider" - - const val BASE_CONTENT_URI = "content://" - -} -``` - -or you can use the annotation in any class you wish. The recommended place would be in a `@Database` placeholder class. This is to simplify some of the declarations and keep it all in one place. Any database this annotated class references **must** extend `ContentProviderDatabase` - -```kotlin -@ContentProvider(authority = TestDatabase.AUTHORITY, - database = TestDatabase::class, - baseContentUri = TestDatabase.BASE_CONTENT_URI) -@Database(name = TestDatabase.NAME, version = TestDatabase.VERSION) -abstract class TestDatabase: ContentProviderDatabase() { - companion object { - - const val NAME = "TestDatabase" - - const val VERSION = 1 - - const val AUTHORITY = "com.dbflow5.test.provider" - - const val BASE_CONTENT_URI = "content://" - - } -} -``` - -### Adding To Manifest - -In other applications or your current's `AndroidManifest.xml` add the **generated $Provider** class: - -```markup - -``` - -`android:exported`: setting this to true, enables other applications to make use of it. - -**True** is recommended for outside application access. - -**Note you must have at least one** `@TableEndpoint` **for it to compile/pass error checking** - -### Adding endpoints into the data - -There are two ways of defining `@TableEndpoint`: - -1. Create an inner class within the `@ContentProvider` annotation. - - 2. Or Add the annotation to a `@Table` and specify the content provider class name \(ex. TestContentProvider\) - -`@TableEndpoint`: links up a query, insert, delete, and update to a specific table in the `ContentProvider` local database. - -Some recommendations: - -1. \(if inside a `@ContentProvider` class\) Name the inner class same as the table it's referencing - -2. Create a `const val ENDPOINT = "{tableName}"` field for reusability - -3. Create `buildUri()` method \(see below\) to aid in creating other ones. - -To define one: - -```kotlin -@TableEndpoint(ContentProviderModel.ENDPOINT) -object ContentProviderModel { - - const val ENDPOINT = "ContentProviderModel" - - fun buildUri(vararg paths: String): Uri { - val builder = Uri.parse(BASE_CONTENT_URI + AUTHORITY).buildUpon() - for (path in paths) { - builder.appendPath(path) - } - return builder.build() - } - - @ContentUri(path = ContentProviderModel.ENDPOINT, - type = ContentUri.ContentType.VND_MULTIPLE + ENDPOINT) - val CONTENT_URI = buildUri(ENDPOINT); - -} -``` - -or via the table it belongs to - -```kotlin -@TableEndpoint(name = ContentProviderModel.NAME, contentProvider = ContentDatabase::class) -@Table(database = ContentDatabase::class, name = ContentProviderModel.NAME, generateContentValues = true) -class ContentProviderModel(@PrimaryKey(autoincrement = true) - var id: Long = 0, - var notes: String? = null, - var title: String? = null) : BaseProviderModel() { - - override val deleteUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val insertUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val updateUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - override val queryUri get() = TestContentProvider.ContentProviderModel.CONTENT_URI - - companion object { - - const val NAME = "ContentProviderModel" - - @ContentUri(path = NAME, type = "${ContentType.VND_MULTIPLE}${NAME}") - val CONTENT_URI = ContentUtils.buildUriWithAuthority(ContentDatabase.AUTHORITY) - } -} -``` - -There are much more detailed usages of the `@ContentUri` annotation. Those will be in a later section. - -### Connect Model operations to the newly created ContentProvider - -There are two kinds of `Model` that connect your application to a ContentProvider that was defined in your app, or another app. Extend these for convenience, however they are not required. - -`BaseProviderModel`: Overrides all `Model` methods and performs them on the `ContentProvider` - -`BaseSyncableProviderModel`: same as above, except it will synchronize the data changes with the local app database as well! - -#### Interacting with the Content Provider - -You can use the `ContentUtils` methods: - -```kotlin -val contentProviderModel: ContentProviderModel = ...; // some instance - -val count = ContentUtils.update(contentResolver, ContentProviderModel.CONTENT_URI, contentProviderModel) - -val uri = ContentUtils.insert(contentResolver, ContentProviderModel.CONTENT_URI, contentProviderModel) - -val count = ContentUtils.delete(contentResolver, someContentUri, contentProviderModel) -``` - -**Recommended** usage is extending `BaseSyncableProviderModel` \(for inter-app usage\) so the local database contains the same data. Otherwise `BaseProviderModel` works just as well. - -```java -MyModel model = new MyModel(); -model.id = 5; -model.load(database()) // queries the content provider - -model.someProp = "Hello" -model.update(database()) // runs an update on the CP - -model.insert(database()) // inserts the data into the CP -``` - -## Advanced Usage - -### Notify Methods - -You can define `@Notify` method to specify a custom interaction with the `ContentProvider` and return a custom `Uri[]` that notifies the contained `ContentResolver`. These methods can have any valid parameter from the `ContentProvider` methods. - -Supported kinds include: 1. Update 2. Insert 3. Delete - -#### Example - -```kotlin -@JvmStatic -@Notify(method = NotifyMethod.UPDATE, -paths = {}) // specify paths that will call this method when specified. -fun onUpdate(context: Context, uri: Uri): Array { - - return arrayOf(...) // return custom uris here -} -``` - -### ContentUri Advanced - -#### Path Segments - -Path segments enable you to "filter" the uri query, update, insert, and deletion by a specific column and a value define by '\#'. - -To specify one, this is an example `path` - -```java -path = "Friends/#/#" -``` - -then match up the segments as: - -```kotlin -segments = [@PathSegment(segment = 1, column = "id"), - @PathSegment(segment = 2, column = "name")] -``` - -And to put it all together: - -```kotlin -@JvmStatic -@ContentUri(type = ContentType.VND_MULTIPLE, -path = "Friends/#/#", -segments = [@PathSegment(segment = 1, column = "id"), - @PathSegment(segment = 2, column = "name")]) -fun withIdAndName(int id, String name): Uri = buildUri(id, name) -``` - diff --git a/usage2/gettingstarted.md b/usage2/gettingstarted.md deleted file mode 100644 index c6833cc3167e518b4268f5dc3da502cc87b962f6..0000000000000000000000000000000000000000 --- a/usage2/gettingstarted.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -description: >- - This section describes how Models and tables are constructed via DBFlow. first - let's describe how to get a database up and running. ---- - -# Getting Started - -## Creating a Database - -In DBFlow, creating a database is as simple as only a few lines of code. DBFlow supports any number of databases, however individual tables and other related files can only be associated with one database. - -```kotlin -@Database(version = 1) -abstract class AppDatabase : DBFlowDatabase() -``` - -The default name of the database is the class name. To change it, read [here](usage/databases.md). - -Writing this file generates \(by default\) a `AppDatabaseAppDatabase_Database.java` file, which contains tables, views, and more all tied to a specific database. This class is automatically placed into the main `GeneratedDatabaseHolder`, which holds potentially many databases and global `TypeConverter`. The name, `AppDatabaseAppDatabase_Database.java`, is generated via {DatabaseClassName}{DatabaseFileName}\_Database - -To learn more about what you can configure in a database, read [here](usage/databases.md) - -## Initialize FlowManager - -DBFlow currently needs an instance of `Context` in order to use it for a few features such as reading from assets, content observing, and generating `ContentProvider`. - -Initialize in your `Application` subclass. You can also initialize it from other `Context` but we always grab the `Application` `Context` \(this is done only once\). - -```kotlin -class ExampleApplication : Application { - - override fun onCreate() { - super.onCreate() - FlowManager.init(this) - } -} -``` - -By default without passing in a `DatabaseConfig`, we construct an `AndroidSQLiteOpenHelper` database instance. To learn more about what you can configure in a database, read [here](usage/databases.md), including providing own database instances. - -Finally, add the custom `Application` definition to the manifest \(with the name that you chose for the class\): - -```markup - - -``` - -A database within DBFlow is only initialized once you call `database()`. If you don't want this behavior or prefer it to happen immediately, modify your `FlowConfig`: - -```kotlin -override fun onCreate() { - super.onCreate() - FlowManager.init(FlowConfig.builder(this) - .openDatabasesOnInit(true) - .build()) -} -``` - -Each `DBFlowDatabase` contains a `TransactionManager`, which runs any `async` transaction on a queue / dispatch system. If you do not like the built-in `DefaultTransactionManager`, or just want to roll your own existing system: - -```kotlin -FlowManager.init(FlowConfig.builder(this) - .database(DatabaseConfig.builder(AppDatabase::class) - .transactionManagerCreator { db -> CustomTransactionManager(db)) - .build())) -``` - -You can define different kinds for each database. To read more on transactions and subclassing `BaseTransactionManager` go [here](usage/storingdata.md) - -## Create Models - -Creating models are as simple as defining the model class, and adding the `@Table` annotation. To read more on this, read [here](usage/models.md). - -**For now**: Models must provide a default, parameterless constructor. Also, all fields must be mutable \(currently, we hope to evolve this requirement in the future\). An example: - -```kotlin -@Table(database = TestDatabase::class) -class Currency(@PrimaryKey(autoincrement = true) var id: Long = 0, - @Column @Unique var symbol: String? = null, - @Column var shortName: String? = null, - @Column @Unique var name: String = "") // nullability of fields are respected. We will not assign a null value to this field. -``` - -## Build Your DAO - -Set up a DAO \(Data Access Object\) to help you interact with your database. Using dependency injection and service locating, we can build better, highly testable code. While not required to use DBFlow, it is **highly** recommended utilize this approach. - -With kotlin, we can utilize it in a powerful way: - -```kotlin -/** -* Create this class in your own database module. -*/ -interface DBProvider { - - val database: T - -} - -interface CurrencyDAO : DBProvider { - - /** - * Utilize coroutines package - */ - fun coroutineRetrieveUSD(): Deferred> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")).queryList(it) - }.defer() - - /** - * Utilize RXJava2 package. - * Also can use asMaybe(), or asFlowable() (to register for changes and continue listening) - */ - fun rxRetrieveUSD(): Single> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - }.asSingle() - - /** - * Utilize Vanilla Transactions. - */ - fun retrieveUSD(): Transaction.Builder> = - database.beginTransactionAsync { - (select from Currency::class - where (Currency_Table.symbol eq "$")) - .queryList(it) - } - - /** - * Utilize Paging Library from paging artifact. - */ - fun pagingRetrieveUSD(): QueryDataSource.Factory> = (select from Currency::class - where (Currency_Table.symbol eq "$")) - .toDataSourceFactory(database) - -} -``` - -DBFlow uses expressive builders to represent and translate to the SQLite language. - -We can represent the query above in SQLite: - -```text -SELECT * FROM Currency WHERE symbol='$'; -``` - -Wherever we perform dependency injection we supply the instance: - -```kotlin -fun provideCurrencyDAO(db: AppDatabase) = object : CurrencyDAO { - override val database: AppDatabase = db -} -``` - -Then in our `ViewModel`, we can inject it via the constructor and utilize it in our queries: - -```kotlin -class SampleViewModel(private currencyDAO: CurrencyDAO) -``` - -We support many kinds of complex and complicated queries using the builder language. To read more about this, see [the wrapper language docs](usage/sqlitewrapperlanguage.md) - -There is much more you can do in DBFlow. Read through the other docs to get a sense of the library. - diff --git a/usage2/including-in-project.md b/usage2/including-in-project.md deleted file mode 100644 index 9c909a4236a0d0e107d100ca3316e0604b94d27a..0000000000000000000000000000000000000000 --- a/usage2/including-in-project.md +++ /dev/null @@ -1,80 +0,0 @@ -# Including In Project - -DBFlow has a number of artifacts that you can include in the project. - -**Kotlin:** Built using the language, the library is super-concise, null-safe and efficient. - -**Annotation Processor**: Generates the necessary code that you don't need to write. - -**Core:** Contains the main annotations and misc classes that are shared across all of DBFlow. - -**DBFlow:** The main library artifact used in conjunction with the previous two artifacts. - -**Coroutines:** Adds coroutine support for queries. - -**RX Java:** Enable applications to be reactive by listening to DB changes and ensuring your subscribers are up-to-date. - -**Paging:** Android architecture component paging library support for queries via `QueryDataSource`. - -**LiveData:** Android architecture LiveData support for queries on table changes. - -**SQLCipher:** Easy database encryption support in this library. - -## Add the jitpack.io repository - -This repo is used to publish the artifacts. It also enables [dynamic builds](https://jitpack.io/docs/), allowing you to specify specific branches or commit hashes of the project to include outside of normal releases. - -```groovy -allProjects { - repositories { - google() - // required to find the project's artifacts - // place last - maven { url "https://www.jitpack.io" } - } -} -``` - -Add artifacts to your project: - -```groovy - apply plugin: 'kotlin-kapt' // only required for kotlin consumers. - - def dbflow_version = "5.0.0-alpha2" - // or 10-digit short-hash of a specific commit. (Useful for bugs fixed in develop, but not in a release yet) - - dependencies { - - // Use if Kotlin user. - kapt "com.github.agrosner.dbflow:processor:${dbflow_version}" - - // Annotation Processor - // if only using Java, use this. If using Kotlin do NOT use this. - annotationProcessor "com.github.agrosner.dbflow:processor:${dbflow_version}" - - // core set of libraries - implementation "com.github.agrosner.dbflow:core:${dbflow_version}" - implementation "com.github.agrosner.dbflow:lib:${dbflow_version}" - - // sql-cipher database encryption (optional) - implementation "com.github.agrosner.dbflow:sqlcipher:${dbflow_version}" - implementation "net.zetetic:android-database-sqlcipher:${sqlcipher_version}@aar" - - // RXJava 2 support - implementation "com.github.agrosner.dbflow:reactive-streams:${dbflow_version}" - - // Kotlin Coroutines - implementation "com.github.agrosner.dbflow:coroutines:${dbflow_version}" - - // Android Architecture Components Paging Library Support - implementation "com.github.agrosner.dbflow:paging:${dbflow_version}" - - // Android Architecture Components LiveData Library Support - implementation "com.github.agrosner.dbflow:livedata:${dbflow_version}" - - // adds generated content provider annotations + support. - implementation "com.github.agrosner.dbflow:contentprovider:${dbflow_version}" - - } -``` - diff --git a/usage2/kotlinsupport.md b/usage2/kotlinsupport.md deleted file mode 100644 index 3ba3b45bca363658b134a7ab4cac74fd5c2791b9..0000000000000000000000000000000000000000 --- a/usage2/kotlinsupport.md +++ /dev/null @@ -1,100 +0,0 @@ -# Kotlin Support - -While this library is written in Kotlin, there are a few Kotlin-specific nuances you must be aware of. - - 1. Default Constructors - 2. Inline Classes - 3. `internal` modifier. - - - -## Default Constructors - -Currently, DBFlow only supports a default `constructor`. - -You must provide default values for all constructor arguments to generate a default constructor, or -provide one yourself: - -```kotlin - -// OK -@Table(database = TestDatabase::class) -class Currency(@PrimaryKey(autoincrement = true) var id: Long = 0, - @Column @Unique var symbol: String? = null, - @Column var shortName: String? = null, - @Column @Unique var name: String = "") - -// Not OK -@Table(database = TestDatabase::class) -class Currency(@PrimaryKey(autoincrement = true) var id: Long, - @Column @Unique var symbol: String?, - @Column var shortName: String?, - @Column @Unique var name: String) - -// OK! -@Table(database = TestDatabase::class) -class Currency(@PrimaryKey(autoincrement = true) var id: Long, - @Column @Unique var symbol: String?, - @Column var shortName: String?, - @Column @Unique var name: String) { - constructor() : this(0L, null, null, "") - -} - - -``` - -### Inline Classes - -A new feature of 1.3, DBFlow does not _quite_ support `inline` classes yet. - -Currently given two `inline` classes with a typical table name: -```kotlin -inline class Password(val value: String) -inline class Email(val value: String) - -@Table(database = TestDatabase::class) -class UserInfo(@PrimaryKey - var email: Email = Email(""), - var password: Password = Password("")) -``` - -Kotlin will generate mangled setter method names so Java consumers cannot utilize them directly. DBFlow -does not yet support "finding" these setters or know that they exist. The hash at the end of the method name -might change or break in future versions, so there is a slight workaround. - -To alleviate this issue: - 1. Define `@set:JvmName` on each property that is inline class with the expected set name - 2. Provide a default, visible constructor (as Kotlin makes this `private` by default). - - -```kotlin -inline class Password(val value: String) -inline class Email(val value: String) - -@Table(database = TestDatabase::class) -class UserInfo(@PrimaryKey - @set:JvmName("setEmail") - var email: Email, - @set:JvmName("setPassword") - var password: Password) { - constructor() : this(Email(""), Password("")) -} -``` - -### Internal - -`internal` modifier is not quite supported yet with DBFlow for columns. Since it -generates a different name for each build variant when compiled, we can not detect the getter/setter -just yet automatically. It is _kind of_ allowed with DBFlow. You will have to -override its generated getter/setter names to match the field name (which defeats purpose of `internal` from a Java consumer angle.) - -```kotlin -@Table(database = TestDatabase::class) -class InternalClass internal constructor(@PrimaryKey - @get:JvmName("getId") - @set:JvmName("setId") - internal var id: String = "") -``` - -Will compile, but now its not really different from a public property. \ No newline at end of file diff --git a/usage2/livedata.md b/usage2/livedata.md deleted file mode 100644 index dcf331953a1d369223d816e97bc7864bebd9d875..0000000000000000000000000000000000000000 --- a/usage2/livedata.md +++ /dev/null @@ -1,38 +0,0 @@ -# Live Data - -The [LiveData](https://developer.android.com/topic/libraries/architecture/livedata#kotlin) artifact for DBFlow -provides a simple way to extend the database `Transaction` into a `LiveData` object. - -## How to Use - -Construct a database transaction and then utilize the Kotlin extension function `liveData { db, queriable -> }` -to map it to a `LiveData instance`. - -```kotlin - -@Table(database = TestDatabase::class) -data class LiveDataModel(@PrimaryKey var id: String = "", - var name: Int = 0) - -fun registerObserver(owner: LifecycleOwner) { - val data: LiveData> = (select from LiveDataModel::class) - .liveData { db, queriable -> queriable.queryList(db) } - - data.observe(owner) { list -> - // called whenever the tables change. - } - - database() - .beginTransactionAsync { db -> - (0..2).forEach { - LiveDataModel(id = "$it", name = it).insert(db) - } - } - .execute() - // any modification to db using model objects or SQLite wrapper will trigger LiveData to update - // and requery the DB. - -} - -``` - diff --git a/usage2/migration4guide.md b/usage2/migration4guide.md deleted file mode 100644 index e5f142a42cf726179e36aef45e2acff3c049a964..0000000000000000000000000000000000000000 --- a/usage2/migration4guide.md +++ /dev/null @@ -1,20 +0,0 @@ -# Migration4Guide - -In 4.0, DBFlow has greatly improved its internals and flexibility in this release. We have removed the `Model` restriction, rewritten the annotation processor completely in Kotlin, and more awesome improvements. - -_Major Changes In this release_ - -1. `PrimaryKey` can have `TypeConverters`, be table-based objects, and all kinds of objects. No real restrictions. -2. `ForeignKey` have been revamped to allow `stubbedRelationship`. This replaces `ForeignKeyContainer`. -3. `Model` interface now includes `load()` to enabled reloading very easily when fields change. -4. All `ModelContainer` implementation + support has been removed. A few reasons pushed the removal, including implementation. Since removing support, the annotation processor is cleaner, easier to maintain, and more streamlined. Also the support for it was not up to par, and by removing it, we can focus on improving the quality of the other features. -5. The annotation processor has been rewritten in Kotlin! By doing so, we reduced the code by ~13%. -6. Removed the `Model` restriction on tables. If you leave out extending `BaseModel`, you _must_ interact with the `ModelAdapter`. -7. We generate much less less code than 3.0. Combined the `_Table` + `_Adapter` into the singular `_Table` class, which contains both `Property` + all of the regular `ModelAdapter` methods. To ease the transition to 4.0, it is named `_Table` but extends `ModelAdapter`. So most use cases / interactions will not break. -8. `Condition` are now `Operator`, this includes `SQLCondition` -> `SQLOperator`, `ConditionGroup` -> `OperatorGroup`. `Operator` are now typed and safer to use. 1. `Operator` now also have `div`, `times`, `rem`, `plus` and `minus` methods. -9. Property class changes: 1. All primitive `Property` classes have been removed. We already boxed the values internally anyways so removing them cut down on method count and maintenance. 2. `BaseProperty` no longer needs to exist, so all of it's methods now exist in `Property` 3. `mod` method is now `rem` \(remainder\) method to match Kotlin 1.1's changes. 4. `dividedBy` is now `div` to match Kotlin operators. 5. `multipliedBy` is now `times` to match Kotlin operators. -10. Rewrote all Unit tests to be more concise, better tested, and cleaner. -11. A lot of bug fixes -12. Kotlin: 1. Added more Kotlin extensions. 2. Most importantly you don't need to use `BaseModel`/`Model` at all anymore if you so choose. There are `Model`-like extension methods that supply the `Model` methods. 3. Updated to version 1.1.1 -13. RXJava1 and RXJava2 support! Can now write queries that return `Observable` and more. - diff --git a/usage2/proguard.md b/usage2/proguard.md deleted file mode 100644 index 398e9895d9c81f5ed5839a8866b9645a8f036eba..0000000000000000000000000000000000000000 --- a/usage2/proguard.md +++ /dev/null @@ -1,10 +0,0 @@ -# Proguard - -Since DBFlow uses annotation processing, which is run pre-proguard phase, the configuration is highly minimal. Also since we combine all generated files into the `GeneratedDatabaseHolder`, any other class generated can be obfuscated. - -```text --keep class * extends com.dbflow5.config.DatabaseHolder { *; } -``` - -This also works on modules from other library projects that use DBFlow. - diff --git a/usage2/rxjavasupport.md b/usage2/rxjavasupport.md deleted file mode 100644 index e4b9b6bd6e12f601d75cc8d79ea7524f5a489483..0000000000000000000000000000000000000000 --- a/usage2/rxjavasupport.md +++ /dev/null @@ -1,93 +0,0 @@ -# RXJavaSupport - -RXJava support in DBFlow is an _incubating_ feature and likely to change over time. We support both RX1 and RX2 and have made the extensions + DBFlow compatibility almost identical - save for the changes and where it makes sense in each version. - -Currently it supports - -1. Any `ModelQueriable` can be wrapped in a `Single`, `Maybe`, or `Flowable` \(to continuously observe changes\). - -2. Single + `List` model `save()`, `insert()`, `update()`, and `delete()`. - -3. Streaming a set of results from a query - -4. Observing on table changes for specific `ModelQueriable` and providing ability to query from that set repeatedly as needed. - -## Getting Started - -Add the separate packages to your project: - -```groovy -dependencies { - - // RXJava3 - compile "com.github.agrosner.dbflow:reactive-streams:${dbflow_version}" - -} -``` - -## Wrapper Language - -We can convert wrapper queries into different kinds of RX operations. - -For a single query: - -Using vanilla transactions: - -```kotlin -(select - from MyTable::class) - .async(db) { d -> queryList(d) } - .execute { _, r ->} -``` - -Using RX: - -```kotlin -(select - from MyTable::class) - .async(db) { d -> queryList(d) } - .asSingle() - .subscribeBy { list -> - // use list - } -``` - -### Observable Queries - -We can easily observe a query for any table changes \(once per transaction\) while it is active and recompute via: - -```kotlin -(select - from MyTable::class) - .asFlowable { db -> queryList(db) } - .subscribeBy { list -> - // use list - } -``` - -This works across joins as well. - -## Model operations - -Operations are as easy as: - -```kotlin -Person(5, "Andrew Grosner") - .rxInsert(db) // rxSave, rxUpdate, etc. - .subscribeBy { rowId -> - - } -``` - -## Query Stream - -We can use RX to stream the result set, one at a time from the `ModelQueriable` using the method `queryStreamResults()`: - -```kotlin -(select from TestModel::class) - .queryStreamResults(db) - .subscribeBy { model -> - - } -``` - diff --git a/usage2/usage/README.md b/usage2/usage/README.md deleted file mode 100644 index 4a8ed12c00d3c1ed7290977fb9e205137864c598..0000000000000000000000000000000000000000 --- a/usage2/usage/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# Main Usage - -DBFlow supports a number of database features that will enhance and decrease time you need to spend coding with databases. We support multiple databases at the same time \(and in separate modules\) as long as there's no shared models. - -What is covered in these docs are not all inclusive, but should give you an idea of how to operate with DBFlow on databases. - -There are a few concepts to familiarize yourself with. We will go more in depth in other sections in this doc. - -**SQLite Wrapper Language:** DBFlow provides a number of convenience methods, extensions, and generated helpers that produce a concise, flowable query syntax. A few examples below: - -```text -List users = SQLite.select() - .from(User.class) - .where(name.is("Andrew Grosner")) - .queryList(); - -SQLite.update(User.class) - .set(name.eq("Andrew Grosner")) - .where(name.eq("Andy Grosner")) - .executeUpdateDelete() - -FlowManager.getDatabase(AppDatabase.class).beginTransactionAsync((DatabaseWrapper wrapper) -> { - // wraps in a SQLite transaction, do something on BG thread. -}); - -CursorResult results = SQLite.select().from(User.class).queryResults(); -try { - for (User user: results) { // memory efficient iterator - - } -} finally { - results.close() -} -``` - -Or in Kotlin: - -```text -val users = (select from User::class where (name `is` "Andrew Grosner")).list - -(update() set (name eq "Andrew Grosner") where (name eq "Andy Grosner")).executeUpdateDelete() - -database().beginTransactionAsync { - -} - -(select from User::class).queryResults().use { results -> - for (user in results) { - - } -} -``` - -**Caching:** DBFlow supports caching in models. Caching them greatly increases speed, but cache carefully as it can lead to problems such as stale data. - -```text -@Table(cachingEnabled = true) -public class User -``` - -**Migrations:** Migrations are made very simple in DBFlow. We only support the kinds that [SQLite provide](https://sqlite.org/lang_altertable.html), but also allow you to modify the data within the DB in a structured way during these. They are also run whenever the `SQLiteOpenHelper` detects a version change in the order of version they specify. - -**Multiple Modules:** DBFlow can be used in library projects, in any number of inner-project modules simultaneously. However these models must reside in separate databases. - -**Relationships:** Not the human kind. We support `@ForeignKey` including multiple foreign keys for 1-1. `@OneToMany` is a manual relationship generated by the compiler between two tables to manage data.`@ManyToMany` generates a join table between two tables for many to many relationships. - -**Views:** Declared like tables, Views \(Virtual Tables\) are supported. - -**Query Models:** Query models have no SQLite connection, but are extremely useful for custom queries or join queries that return a result set not mappable to any model you currently use for tables. - -**Encrypted Databases:** DBFlow supports database encryption for security using SQLCipher through a separate, easy-to-integrate artifact. - -**Indexes:** A SQLite feature that drastically improves query performance on large datasets. Dead-easy to implement. - -**Reactive:** Easily listen to changes in database data via `ModelNotifier` system. - -**Transaction Management:** Place all transactions and retrievals on same background thread for maximum efficiency and to prevent UI-hiccups. - -**Type Converters**: Have custom data? Define a `TypeConverter` to map it to SQLite data types. - diff --git a/usage2/usage/databases.md b/usage2/usage/databases.md deleted file mode 100644 index c37e927a044e2e04e429c48a2e231edc7640a614..0000000000000000000000000000000000000000 --- a/usage2/usage/databases.md +++ /dev/null @@ -1,153 +0,0 @@ -# Databases - -This section describes how databases are created in DBFlow and some more advanced features. - -## Creating a Database - -In DBFlow, creating a database is as simple as only a few lines of code. DBFlow supports any number of databases, however individual tables and other related files can only be associated with one database. **Note**: Starting with DBFlow 5.0, databases are required to extend `DBFlowDatabase`. - -```kotlin -@Database(version = 1) -abstract class AppDatabase : DBFlowDatabase() -``` - -## Referencing the Database - -To grab a reference to the DB object: - -```kotlin -val db = database() - -// can utilize in a closure -database { db -> - model.save(db) - - db.beginTransactionAsync { d -> } -} - -// or old-school way -val db = FlowManager.getDatabase(AppDatabase::class.java) -``` - -## Initialization - -To specify a custom **name** to the database, in previous versions of DBFlow \(< 4.1.0\), you had to specify it in the `@Database` annotation. As of 5.0 now you pass it in the initialization of the `FlowManager`: - -```kotlin -FlowManager.init(context) { - database { - databaseName("AppDatabase") - } - } -``` - -To dynamically change the database name, call: - -```kotlin -database() - .reopen(DatabaseConfig.builder(AppDatabase::class) - .databaseName("AppDatabase-2") - .build()) -``` - -This will close the open DB, reopen the DB, and replace previous `DatabaseConfig` with this new one. Ensure that you persist the changes to the `DatabaseConfig` somewhere as next time app is launched and DBFlow is initialized, the new config would get overwritten. - -### In Memory Databases - -As with **name**, in previous versions of DBFlow \(< 5.0\), you specified `inMemory` in the `@Database` annotation. Starting with 5.0 that is replaced with: - -```kotlin -FlowManager.init(context) { - inMemoryDatabase { - databaseName("AppDatabase") - } -} -``` - -This will allow you to use in-memory databases in your tests, while writing to disk in your apps. Also if your device the app is running on is low on memory, you could also swap the DB into memory by calling `reopen(DatabaseConfig)` as explained above. - -## Database Migrations - -Database migrations are run when upon open of the database connection, the version number increases on an existing database. - -It is preferred that `Migration` files go in the same file as the database, for organizational purposes. An example migration: - -```kotlin -@Database(version = 2) -abstract class AppDatabase : DBFlowDatabase() { - - @Migration(version = 2, database = MigrationDatabase::class) - class AddEmailToUserMigration : AlterTableMigration(User::class.java) { - - override fun onPreMigrate() { - addColumn(SQLiteType.TEXT, "email") - } - } -} -``` - -This simple example adds a column to the `User` table named "email". In code, just add the column to the `Model` class and this migration runs only on existing dbs. To read more on migrations and more examples of different kinds, visit the [page](migrations.md). - -## Advanced Database features - -This section goes through features that are for more advanced use of a database, and may be very useful. - -### Prepackaged Databases - -To include a prepackaged database for your application, simply include the ".db" file in `src/main/assets/{databaseName}.db`. On creation of the database, we copy over the file into the application for usage. Since this is prepackaged within the APK, we cannot delete it once it's copied over, which can bulk up your raw APK size. _Note_ this is only copied over on initial creation of the database for the app. - -### Global Conflict Handling - -In DBFlow when an INSERT or UPDATE are performed, by default, we use `NONE`. If you wish to configure this globally, you can define it to apply for all tables from a given database: - -```kotlin -@Database(version = 2, insertConflict = ConflictAction.IGNORE, updateConflict = ConflictAction.REPLACE) -abstract class AppDatabase : DBFlowDatabase() -``` - -These follow the SQLite standard [here](https://www.sqlite.org/conflict.html). - -### Integrity Checking - -Databases can get corrupted or in an invalid state at some point. If you specify `consistencyChecksEnabled=true` It runs a `PRAGMA quick_check(1)` whenever the database is opened. If it fails, you should provide a backup database that it will copy over. If not, **we wipe the internal database**. Note that during this time in case of failure we create a **third copy of the database** in case transfer fails. - -### Custom FlowSQLiteOpenHelper - -For variety of reasons, you may want to provide your own `FlowSQLiteOpenHelper` to manage database interactions. To do so, you must implement `OpenHelper`, but for convenience you should extend `AndroidSQLiteOpenHelper` \(for Android databases\), or `SQLCipherOpenHelper` for SQLCipher. Read more [here](../advanced-usage/sqlciphersupport.md) - -```kotlin -class CustomFlowSQliteOpenHelper(context: Contect, databaseDefinition: DatabaseDefinition, listener: DatabaseHelperListener) : FlowSQLiteOpenHelper(context, databaseDefinition, listener) -``` - -Then in your `DatabaseConfig`: - -```kotlin -FlowManager.init(context) { - database { - openHelper(::CustomFlowSQliteOpenHelper) - } -} -``` - -### Database Configuration DSL - -As of 5.0.0-alpha2, you can configure a database via a DSL rather than use the `FlowConfig.Builder` , `DatabaseConfig.Builder`, and `TableConfig.Builder`. This allows more readable, expressive syntax. - -**Initializing DBFlow:** - -```kotlin -FlowManager.init(context) { - // this is FlowConfig.Builder - database { - // this is DatabaseConfig.Builder - table { - // this is TableConfig.Builder - } - } - // other module dbs - databaseHolder() -} -``` - -By utilizing Kotlin DSL, this code is more straightforward, concise, and readable. - diff --git a/usage2/usage/migrations.md b/usage2/usage/migrations.md deleted file mode 100644 index 363e8b96ae3cb8ac4e9dd88f9df342aae0e75565..0000000000000000000000000000000000000000 --- a/usage2/usage/migrations.md +++ /dev/null @@ -1,140 +0,0 @@ -# Migrations - -In this section we will discuss how migrations work, how each of the provided migration classes work, and how to create your own custom one. - -There are two kinds of migrations that DBFlow supports: Script-based SQL files and class annotation-based migrations. - -## How Migrations Work - -In SQL databases, migrations are used to modify or change existing database schema to adapt to changing format or nature of stored data. In SQLite we have a limited ability compared to SQL to modify tables and columns of an existing database. There are only two kinds of modifications that exist: rename table and add a new column. - -In DBFlow, migrations are not only used to modify the _structure_ of the database, but also other operations such as insert data into a database \(for pre-population\), or add an index on a specific table. - -Migrations are only run on an existing database _except_ for the "0th" migration. Read [initial database setup](migrations.md#initial-database-setup) - -### Migration Classes - -We recommend placing any `Migration` inside an associated `@Database` class so it's apparent the migration is tied to it. An example migration class: - -```kotlin -@Database(version = 2) -object AppDatabase { - - @Migration(version = 2, database = AppDatabase::class, priority = 1) - class Migration2 : BaseMigration() { - - override fun migrate(database: DatabaseWrapper) { - // run some code here - (update() - set Employee_Table.status.eq("Invalid") - where Employee_Table.job.eq("Laid Off")) - .execute(database) // required to pass wrapper in migration - } - } -} -``` - -The classes provide the ability to set a `priority` on the `Migration` so that an order is established if more than one migration is to run on the same DB version upgrade. They are in reverse order - lower the priority, that one will execute first. - -`Migration` have three methods: - -1. `onPreMigrate()` - called first, do setup, and construction here. - -2. `migrate()` -> called with the `DatabaseWrapper` specified, this is where the actual migration code should execute. - -3. `onPostMigrate()` -> perform some cleanup, or any notifications that it was executed. - -### Migration files - -DBFlow also supports `.sql` migration files. The rules on these follows must be followed: - -1. Place them in `assets/migrations/{DATABASE_NAME}/{versionNumber}.sql`. So that an example `AppDatabase` migration for version 2 resides in `assets/migrations/AppDatabase/2.sql` - -2. The file can contain any number of SQL statements - they are executed in order. Each statement must be on a single line or multiline and must end with `;` - -3. Comments are allowed as long as they appear on an individual file with standard SQLite comment syntax `--` - -### Prevent Recursive Access to the DB - -Since `Migration` occur when the database is opening, we cannot recursively access the database object in our models, SQLite wrapper statements, and other classes in DBFlow that are inside a `Migration`. - -To remedy that, DBFlow comes with support to pass the `DatabaseWrapper` into almost all places that require it: - -1. All query language `BaseQueriable` objects such as `Select`, `Insert`, `Update`, `Delete`, etc have methods that take in the `DatabaseWrapper` - -2. Any subclass of `BaseModel` \(`Model` does not provide the methods for simplicity\) - -**Note:** As of 5.0.0-alpha2, all DB methods require the `DatabaseWrapper` object to be passed into their operations. This is for explicit safety. - -### Initial Database Setup - -DBFlow supports `Migration` that run on version "0" of a database. When Android opens a `SQLiteDatabase` object, if the database is created, DBFlow calls on a `Migration` of -1 to 0th version. In this case, any `Migration` run at `version = 0` will get called. Once a database is created, this migration will not run again. So if you had an existent database at version 1, and changed version to 2, the "0th" `Migration` is not run because the old version the database would have been 1. - -## Provided Migration Classes - -In DBFlow we provide a few helper `Migration` subclasses to provide default and easier implementation: 1. `AlterTableMigration` - -2. `IndexMigration/IndexPropertyMigration` - -3. `UpdateTableMigration` - -### AlterTableMigration - -The _structural_ modification of a table is brought to a handy `Migration` subclass. - -It performs both of SQLite supported operations: - -1. Rename tables - -2. Add columns. - -For renaming tables, you should rename the `Model` class' `@Table(name = "{newName}")` before running this `Migration`. The reason is that DBFlow will know the new name only and the existing database will get caught up on it through this migration. Any new database created on a device will automatically have the new table name. - -For adding columns, we only support `SQLiteType` \(all supported ones [here](https://www.sqlite.org/datatype3.html)\) operations to add or remove columns. This is to enforce that the columns are created properly. If a column needs to be a `TypeConverter` column, use the database value from it. We map the associated type of the database field to a `SQLiteType` in [SQLiteType.kt](https://github.com/agrosner/DBFlow/tree/fb3739caa4c894d50fd0d7873c70a33416c145e6/dbflow/src/main/java/com/dbflow5/sql/SQLiteType.kt). So if you have a `DateConverter` that specifies a `Date` column converted to `Long`, then you should look up `Long` in the `Map`. In this case `Long` converts to `INTEGER`. - -```kotlin -@Migration(version = 2, database = AppDatabase::class) -class Migration2 : AlterTableMigration(AModel::class) { - - override fun onPreMigrate() { - addColumn(SQLiteType.TEXT, "myColumn") - addColumn(SQLiteType.REAL, "anotherColumn") - } -} -``` - -### Index Migrations - -An `IndexMigration` \(and `IndexPropertyMigration`\) is used to structurally activate an `Index` on the database at a specific version. See [here](../advanced-usage/indexing.md) for information on creating them. - -`IndexMigration` does not require an `IndexProperty` to run, while `IndexPropertyMigration` makes use of the property to run. - -An `IndexMigration`: - -```kotlin -@Migration(version = 2, priority = 0, database = MigrationDatabase::class) -class IndexMigration2(override val name = "TestIndex") : IndexMigration(MigrationModel::class) -``` - -An `IndexPropertyMigration`: - -```kotlin -@Migration(version = 2, priority = 1, database = MigrationDatabase.class) -class IndexPropertyMigration2(override val indexProperty = IndexModel_Table.index_customIndex) : IndexPropertyMigration -``` - -### Update Table Migration - -A simple wrapper around `Update`, provides simply a default way to update data during a migration. - -```java -@Migration(version = 2, priority = 2, database = MigrationDatabase.class) -class UpdateMigration2 extends UpdateTableMigration(MigrationModel::class) { - - init { - // preselect update query set. - set(MigrationModel_Table.name.eq("New Name")); - } -} -``` - diff --git a/usage2/usage/models.md b/usage2/usage/models.md deleted file mode 100644 index 695bcb960738658bfcad74f820d4156488bedcdc..0000000000000000000000000000000000000000 --- a/usage2/usage/models.md +++ /dev/null @@ -1,116 +0,0 @@ -# Models - -In DBFlow we dont have any restrictions on what your table class is. We do, however if you use Java, we recommend you subclass `BaseModel` on your highest-order base-class, which provides a default implementation for you. Otherwise utilize a kotlin extension method on `Any`. - -```kotlin -myTableObject.save(db) -``` - -## Columns - -By default, DBFlow inclusdes all properties as columns. For other kinds of fields, they must contain either `@PrimaryKey` or `@ForeignKey` to be used in tables. However this still requires you to specify at least one `@PrimaryKey` field. You can then explicitly ignore fields via the `@ColumnIgnore` annotation if necessary. You can turn off all fields and make it explicit using `@Table(allFields = false)` - -In Kotlin, Column properties must be public and `var` for now. In future versions, we hope to support Kotlin constructors without default arguments. For now, all must be `var` and provide a default constructor. We respect nullability of the properties and won't assign `null` to them if they're not nullable, but they must provide a default value. - -In Java, Columns can be `public`, package-private, or `private`. `private` fields **must** come with `public` java-bean-style getters and setters. Package private used in other packages generate a `_Helper` class which exposes a method to call these fields in an accessible way. This has some overhead, so consider making them with `public` get/set or public. - -Here is an example of a "nice" `Table`: - -```kotlin -@Table(database = AppDatabase::class) -class Dog(@PrimaryKey var id: Int = 0, var name: String? = null) -``` - -Columns have a wide-range of supported types in the `Model` classes: **Supported Types**: - -1. all primitives including `Char`,`Byte`, `Short`, and `Boolean`. -2. All Kotlin nullable primitives \(java boxed\). -3. `String`, `Date`, `java.sql.Date`, `Calendar`, `com.dbflow5.data.Blob`, `Boolean` -4. Custom data types via a [TypeConverter](typeconverters.md) -5. `Model` as fields, but only as `@PrimaryKey` and/or `@ForeignKey` -6. `@ColumnMap` objects that flatten an object into the current table. Just like a `@ForeignKey`, but without requiring a separate table. \(4.1.0+\). **Note:** Avoid nesting more than one object, as the column count could get out of control. - -**Unsupported Types**: - -1. `List` : List columns are not supported and not generally proper for a relational database. However, you can get away with a non-generic `List` column via a `TypeConverter`. But again, avoid this if you can. -2. Anything that is generically typed \(even with an associated `TypeConverter`\). If you need to include the field, subclass the generic object and provide a `TypeConverter`. - -## Inherited Columns - -Since we don't require extension on `BaseModel` directly, tables can extend non-model classes and inherit their fields directly \(given proper accessibility\) via the `@InheritedColumn` annotation \(or `@InheritedPrimaryKey` for primary keys\): - -```java -@Table(database = AppDatabase.class, - inheritedColumns = {@InheritedColumn(column = @Column, fieldName = "name"), - @InheritedColumn(column = @Column, fieldName = "number")}, - inheritedPrimaryKeys = {@InheritedPrimaryKey(column = @Column, - primaryKey = @PrimaryKey, - fieldName = "inherited_primary_key")}) -public class InheritorModel extends InheritedModel implements Model { -``` - -**Note:** This implementation is not recommended for most users. If you do not control the type directly, the inherited class may change in incompatible ways. - -## Primary Keys - -DBFlow supports multiple primary keys, right out of the box. Simply create a table with multiple `@PrimaryKey`: - -```kotlin -@Table(database = AppDatabase::class) -class Dog(@PrimaryKey var name: String = "", @PrimaryKey var breed: String = "") -``` - -If we want an auto-incrementing key, you specify `@PrimaryKey(autoincrement = true)`, but only one of these kind can exist in a table and you cannot mix with regular primary keys. - -## Unique Columns - -DBFlow has support for SQLite `UNIQUE` constraint \(here for documentation\)\[[http://www.tutorialspoint.com/sqlite/sqlite\_constraints.htm](http://www.tutorialspoint.com/sqlite/sqlite_constraints.htm)\]. - -Add `@Unique` annotation to your existing `@Column` and DBFlow adds it as a constraint when the database table is first created. This means that once it is created you should not change or modify this. - -We can _also_ support multiple unique clauses in order to ensure any combination of fields are unique. For example: - -To generate this in the creation query: - -```text -UNIQUE('name', 'number') ON CONFLICT FAIL, UNIQUE('name', 'address') ON CONFLICT ROLLBACK -``` - -We declare the annotations as such: - -```kotlin -@Table(database = AppDatabase::class, - uniqueColumnGroups = [@UniqueGroup(groupNumber = 1, uniqueConflict = ConflictAction.FAIL), - @UniqueGroup(groupNumber = 2, uniqueConflict = ConflictAction.ROLLBACK)]) -class UniqueModel( - @PrimaryKey @Unique(unique = false, uniqueGroups = [1,2]) - var name: String = "", - @Column @Unique(unique = false, uniqueGroups = [1]) - var number: String = "", - @Column @Unique(unique = false, uniqueGroups = [2]) - var address: String = "") -``` - -The `groupNumber` within each defined `uniqueColumnGroups` with an associated `@Unique` column. We need to specify `unique=false` for any column used in a group so we expect the column to be part of a group. If true as well, the column will _also_ alone be unique. - -## Default Values - -**Not to be confused with Kotlin default values.** This only applies when fields are marked as `nullable`. When fields are non null in kotlin, we utilize the default constructor value when it is set, so when the column data is `null` from a `Cursor`, we do not override the initial assignment. - -DBFlow supports default values in a slightly different way than SQLite does. Since we do not know exactly the intention of missing data when saving a `Model`, since we group all fields, `defaultValue` specifies a value that we replace when saving to the database when the value of the field is `null`. - -This feature only works on Boxed primitive and the `DataClass` equivalent of objects \(such as from TypeConverter\), such as String, Integer, Long, Double, etc. **Note**: If the `DataClass` is a `Blob`, unfortunately this will not work. For `Boolean` classes, use "1" for true, "0" for false. - -```kotlin -@Column(defaultValue = "55") -var count: Int, -@Column(defaultValue = "\"this is\"") -var test: String, -@Column(defaultValue = "1000L") -var date: Date, -@Column(defaultValue = "1") -var aBoolean: Boolean, -``` - -**Note:** DBFlow inserts its literal value into the `ModelAdapter` for the table so any `String` must be escaped. - diff --git a/usage2/usage/modelviews.md b/usage2/usage/modelviews.md deleted file mode 100644 index 666eb9d6e1836964245eb8945e90be07a7306a60..0000000000000000000000000000000000000000 --- a/usage2/usage/modelviews.md +++ /dev/null @@ -1,52 +0,0 @@ -# Views - -A `ModelView` is a SQLite representation of a `VIEW`. Read official SQLite docs [here](https://www.sqlite.org/lang_createview.html) for more information. - -As with SQLite a `ModelView` cannot insert, update, or delete itself as it's read-only. It is a virtual "view" placed on top of a regular table as a prepackaged `Select` statement. In DBFlow using a `ModelView` should feel familiar and be very simple. - -```kotlin -@ModelView(database = TestDatabase::class) -class TestModelView(@Column modelOrder: Long = 0L) { - - companion object { - @ModelViewQuery @JvmStatic - val query = (select from TestModel2::class where TestModel2_Table.model_order.greaterThan(5)) - } -} -``` - -You can also specify the query as a property getter or function: - -```kotlin -companion object { - @ModelViewQuery @JvmStatic - val query get() = (select from TestModel2::class where TestModel2_Table.model_order.greaterThan(5)) - - @ModelViewQuery @JvmStatic - fun getQuery() = (select from TestModel2::class where TestModel2_Table.model_order.greaterThan(5)) -} -``` - -To specify the query that a `ModelView` creates itself with, we _must_ define a public static final field annotated with `@ModelViewQuery`. This tells DBFlow what field is the query. This query is used only once when the database is created \(or updated\) to create the view. - -The full list of limitations/supported types are: - -1. Only `@Column`/`@ColumnMap` are allowed - -2. No `@PrimaryKey` or `@ForeignKey` - -3. Supports all fields, and accessibility modifiers that `Model` support - -4. Does not support `@InheritedField`, `@InheritedPrimaryKey` - -5. Basic, type-converted `@Column`. - -6. **Cannot**: update, insert, or delete - -`ModelView` are used identically to `Model` when retrieving from the database: - -```kotlin -(select from TestModelView::class - where ...) // ETC -``` - diff --git a/usage2/usage/observability.md b/usage2/usage/observability.md deleted file mode 100644 index d825a0e7fb851c581f5cf6518ee4e5b2d3a88489..0000000000000000000000000000000000000000 --- a/usage2/usage/observability.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -description: >- - DBFlow provides a flexible way to observe changes on models and tables in this - library. ---- - -# Observability - -By default, DBFlow supports direct [model notification](observability.md#direct-changes) via `DirectModelNotifier`. - -Also, the `DBFlowDatabase` class provides a `TableObserver` object, which provides observability backbone for `QueryDataSource` \(Paging extensions\), `LiveData`, and `RXJava3` support. - -Also with configuration, DBFlow can use the [`ContentResolver`](https://developer.android.com/reference/android/content/ContentResolver.html) to send changes through the android system. We then can utilize [`ContentObserver`](http://developer.android.com/reference/android/database/ContentObserver.html) to listen for these changes via the `FlowContentObserver` - -## Direct Changes - -We must use the shared instance of the `DirectModelNotifier` since if we do not, your listeners will not receive callbacks. - -Next register for changes on the `DirectModelNotifier`: - -```kotlin -DirectModelNotifier.get().registerForModelChanges(User::class, object: ModelChangedListener { - override fun onModelChanged(model: User, action: ChangeAction) { - // react to model changes - } - - override fun onTableChanged(action: ChangeAction) { - // react to table changes. - } - }) -``` - -Then unregister your model change listener when you don't need it anymore \(to prevent memory leaks\): - -```kotlin -DirectModelNotifier.get().unregisterForModelChanges(User::class, modelChangedListener); -``` - -### TableObserver - -The `TableObserver` is a more efficient mechanism for tracking table-level changes on the DB. It utilizes triggers under the hood to track table changes during operations \(but only with active Table observers used\). It is useful for providing reactive queries that will autoreload when data changes. - -**Note:** You must execute a db transaction to automatically check for table changes in observers. - -```kotlin -// wire up the observer (example uses LiveData) -(select from MyTable::class - where name.like("Andrew%")) - .liveData { db, q -> q.queryList(db) - .observe(lifecycle) { value -> - - } - -// perform some transaction -(update() set name.eq("Andrew") where name.eq("Mike")) - .async(db) { d -> execute(d) } - .execute { _, result -> - Log.w("MyTable", "Updated ${result} rows") - } -``` - -To manually access it \(when you need to update it outside of a transaction\): - -```kotlin -database().tableObserver.checkForTableUpdates() -``` - -### FlowContentObserver - -To use a `FlowContentObserver` instead of direct changes, we override the default `ModelNotifier`: - -```kotlin -FlowManager.init(FlowConfig.Builder(context) - .database(DatabaseConfig.Builder(TestDatabase.class) - .modelNotifier(ContentResolverNotifier(context, authority)) - .build()).build()) -``` - -The content observer converts each model passed to it into `Uri` format that describes the `Action`, primary keys, and table of the class that changed. - -A model: - -```kotlin -@Table(database = AppDatabase.class) -class User(@PrimaryKey var id: Int = 0, @Column var name: String = "") -``` - -with data: - -```kotlin -User(55, "Andrew Grosner").delete() -``` - -converts to: - -```text -dbflow://%60User%60?%2560id%2560=55#DELETE -``` - -Then after we register a `FlowContentObserver`: - -```kotlin -val observer = FlowContentObserver() - -observer.registerForContentChanges(context, User::class) - -// do something here -// unregister when done -observer.unregisterForContentChanges(context) -``` - -### Model Changes - -It will now receive the `Uri` for that table. Once we have that, we can register for model changes on that content: - -```kotlin -observer.addModelChangeListener(object : OnModelStateChangedListener { - override fun onModelStateChanged(table: Class<*>?, - action: ChangeAction, - primaryKeyValues: Array) { - // do something here - } -}) -``` - -The method will return the `Action` which is one of: - -1. `SAVE` \(will call `INSERT` or `UPDATE` as well if that operation was used\) - -2. `INSERT` - -3. `UPDATE` - -4. `DELETE` - -The `SQLOperator[]` passed back specify the primary column and value pairs that were changed for the model. - -If we want to get less granular and just get notifications when generally a table changes, read on. - -### Register for Table Changes - -Table change events are similar to `OnModelStateChangedListener`, except that they only specify the table and action taken. These get called for any action on a table, including granular model changes. We recommend batching those events together, which we describe in the next section. - -```kotlin -addOnTableChangedListener(object : OnTableChangedListener { - override fun onTableChanged(tableChanged: Class<*>?, action: ChangeAction) { - // perform an action. May get called many times! Use batch transactions to combine them. - } -}) -``` - -### Batch Up Many Events - -Sometimes we're modifying tens or hundreds of items at the same time and we do not wish to get notified for _every_ one but only once for each _kind_ of change that occurs. - -To batch up the notifications so that they fire all at once, we use batch transactions: - -```kotlin -val observer = FlowContentObserver() -observer.registerForContentChanges(context, User::class.java) - -db.beginTransactionAsync(users.fastInsert().build()) - .execute(ready = { observer.beginTransaction() }, - completion = { observer.endTransactionAndNotify() }) -``` - -Batch interactions will store up all unique `Uri` for each action \(these include `@Primary` key of the `Model` changed\). When `endTransactionAndNotify()` is called, all those `Uri` are called in the `onChange()` method from the `FlowContentObserver` as expected. - -If we are using `OnTableChangedListener` callbacks, then by default we will receive one callback per `Action` per table. If we wish to only receive a single callback, set `setNotifyAllUris(false)`, which will make the `Uri` all only specify `CHANGE`. - -## - diff --git a/usage2/usage/relationships.md b/usage2/usage/relationships.md deleted file mode 100644 index 52e7d56b80163b640963ecdf2b14b4e77fdf262b..0000000000000000000000000000000000000000 --- a/usage2/usage/relationships.md +++ /dev/null @@ -1,172 +0,0 @@ -# Relationships - -We can link `@Table` in DBFlow via 1-1, 1-many, or many-to-many. For 1-1 we use `@PrimaryKey`, for 1-many we use `@OneToMany`, and for many-to-many we use the `@ManyToMany` annotation. - -## One To One - -DBFlow supports multiple `@ForeignKey` right out of the box as well \(and for the most part, they can also be `@PrimaryKey`\). - -```kotlin -@Table(database = AppDatabase::class) -class Dog(@PrimaryKey var name: String, - @ForeignKey(tableClass = Breed::class) - @PrimaryKey var breed: String, - @ForeignKey var owner: Owner? = null) -``` - -`@ForeignKey` can only be a subset of types: - -1. `Model` - -2. Any field not requiring a `TypeConverter`. If not a `Model` or a table class, you _must_ specify the `tableClass` it points to. - -3. Cannot inherit `@ForeignKey` from non-model classes \(see [Inherited Columns](models.md#inherited-columns)\) - -If you create a circular reference \(i.e. two tables with strong references to `Model` as `@ForeignKey` to each other\), read on. - -## Stubbed Relationships - -For efficiency reasons we recommend specifying `@ForeignKey(stubbedRelationship = true)`. What this will do is only _preset_ the primary key references into a table object. This only works with models that have fully mutable constructor properties. - -All other fields will not be set. If you need to access the full object, you will have to call `modelAdapter().load()` . - -From our previous example of `Dog`, instead of using a `String` field for **breed** we recommended by using a `Breed`. It is nearly identical, but the difference being we would then only need to call `load()` on the reference and it would query the `Breed` table for a row with the `breed` id. This also makes it easier if the table you reference has multiple primary keys, since DBFlow will handle the work for you. - -Multiple calls to `load()` will query the DB every time, so call when needed. Also if you don't specify `@Database(foreignKeyConstraintsEnforced = true)`, calling `load()` may not have any effect. Essentially without enforcing `@ForeignKey` at a SQLite level, you can end up with floating key references that do not exist in the referenced table. - -In normal circumstances, for every load of a `Dog` object from the database, we would also do a load of related `Owner`. This means that even if multiple `Dog` say \(50\) all point to same owner we end up doing 2x retrievals for every load of `Dog`. Replacing that model field of `Owner` with a stubbed relationship prevents the extra N lookup time, leading to much faster loads of `Dog`. - -**Note:** if you need more detailed information, you will still need to load the full data on each individual object. - -**Note**: using stubbed relationships also helps to prevent circular references that can get you in a `StackOverFlowError` if two tables strongly reference each other in `@ForeignKey`. - -Our modified example now looks like this: - -```kotlin -@Table(database = AppDatabase::class) -class Dog(@PrimaryKey var name: String, - @ForeignKey(stubbedRelationship = true) - @PrimaryKey var breed: Breed? = null, - @ForeignKey(stubbedRelationship = true) - var owner: Owner? = null) -``` - -## One To Many - -In DBFlow, `@OneToMany` is an annotation that you provide to a method in your `Model` class that will allow management of those objects during CRUD operations. This can allow you to combine a relationship of objects to a single `Model` to happen together on load, save, insert, update, and deletion. - -```kotlin -@Table(database = ColonyDatabase::class) -class Queen(@PrimaryKey(autoincrement = true) - var id: Long = 0, - var name: String? = null, - @ForeignKey(saveForeignKeyModel = false) - var colony: Colony? = null) : BaseModel() { - - @get:OneToMany - val ants: List? by oneToMany { select from Ant::class where Ant_Table.queen_id.eq(id) } - -} -``` - -**Note:** This is not recommended to use heavily. It impacts performance exponentially and only recommended if you have a small set of parent objects that reference a subset of items in the DB. - -### Efficient Methods - -When using `@ManyToMany`, by default we skip the `Model` methods in each retrieved `Ant` \(in this example\). If you have nested `@ManyToMany` \(which should strongly be avoided\), you can turn off the efficient operations. Call `@OneToMany(efficientMethods = false)` and it will instead loop through each model and perform `save()`, `delete()`, etc when the parent model is called. - -### Custom ForeignKeyReferences - -When simple `@ForeignKey` annotation is not enough, you can manually specify references for your table: - -```kotlin -@ForeignKey(saveForeignKeyModel = false, -references = {ForeignKeyReference(columnName = "colony", foreignKeyColumnName = "id")}) -var colony: Colony? = null; -``` - -By default not specifying references will take each field and append "${foreignKeyFieldName}\_${ForeignKeyReferenceColumnName}" to make the reference column name. So by default the previous example would use `colony_id` without references. With references it becomes `colony`. - -## Many To Many - -In DBFlow many to many is done via source-gen. A simple table: - -```kotlin -@Table(database = AppDatabase::class) -@ManyToMany(referencedTable = Follower::class) -class User(@PrimaryKey var id: Int = 0, @PrimaryKey var name: String = "") -``` - -Generates a `@Table` class named `User_Follower`, which DBFlow treats as if you coded the class yourself!: - -```java -@Table( - database = TestDatabase.class -) -public final class User_Follower extends BaseModel { - @PrimaryKey( - autoincrement = true - ) - long _id; - - @ForeignKey( - saveForeignKeyModel = false - ) - Follower follower; - - @ForeignKey( - saveForeignKeyModel = false - ) - User user; - - public final long getId() { - return _id; - } - - public final Followers getFollower() { - return follower; - } - - public final void setFollower(Follower param) { - follower = param; - } - - public final Users getUser() { - return user; - } - - public final void setUser(User param) { - user = param; - } -} -``` - -This annotation makes it very easy to generate "join" tables for you to use in the app for a ManyToMany relationship. It only generates the table you need. To use it you must reference it in code as normal. - -**Note**: This annotation is only a helper to generate tables that otherwise you would have to write yourself. It is expected that management still is done by you, the developer. - -### Custom Column Names - -You can change the name of the columns that are generated. By default they are simply lower case first letter version of the table name. - -`referencedTableColumnName` -> Refers to the referenced table. - -`thisTableColumnName` -> Refers to the table that is creating the reference. - -### Multiple ManyToMany - -You can also specify `@MultipleManyToMany` which enables you to define more than a single `@ManyToMany` relationship on the table. - -A class can use both: - -```kotlin -@Table(database = TestDatabase::class) -@ManyToMany(referencedTable = TestModel1::class) -@MultipleManyToMany({@ManyToMany(referencedTable = TestModel2::class), - @ManyToMany(referencedTable = TestModel3::class)}) -class ManyToManyModel( - @PrimaryKey var name: String = "", - @PrimaryKey var id: Int = 0, - @PrimaryKey var anotherColumn: Char? = null) -``` - diff --git a/usage2/usage/retrieval.md b/usage2/usage/retrieval.md deleted file mode 100644 index 0d400c7d20eb295665ea424ed98e00cd0a57c27e..0000000000000000000000000000000000000000 --- a/usage2/usage/retrieval.md +++ /dev/null @@ -1,78 +0,0 @@ -# Retrieval - -DBFlow provides a few ways to retrieve information from the database. Through the `Model` classes we can map this information to easy-to-use objects. - -DBFlow provides a few different ways to retrieve information from the database. We can retrieve synchronously or asynchronous \(preferred\). - -We can also use `ModelView` \([read here](modelviews.md)\) and `@Index` \([read here](../advanced-usage/indexing.md)\) to perform faster retrieval on a set of data constantly queried. - -## Synchronous Retrieval - -Using the [SQLite query language](sqlitewrapperlanguage.md) we can retrieve data easily and expressively. To perform it synchronously: - -```kotlin -databaseForTable { db -> - // list - val employees = (select from Employee::class).queryList(db) - - // single result, we apply a limit(1) automatically to get the result even faster. - val employee: Employee? = (select from Employee::class - where Employee_Table.name.eq("Andrew Grosner")).querySingle(db) - - // can require result to get non-null if you know it exists - // throws a SQLiteException if missing - val employee: Employee? = (select from Employee::class - where Employee_Table.name.eq("Andrew Grosner")).requireSingle(db) - - // get a custom list - val employees: List = (select from Employee::class) - .queryCustomList(database) - - // custom object - val anotherObject: AnotherTable? = (select from Employee::class - where(Employee_Table.name.eq("Andrew Grosner"))) - .queryCustomSingle() - - // require custom object - val anotherObject: AnotherTable = (select from Employee::class - where(Employee_Table.name.eq("Andrew Grosner"))) - .requireCustomSingle() -} -``` - -To query custom objects or lists, see how to do so in [QueryModel](../advanced-usage/querymodels.md). - -Also you can query a `FlowCursorList`/`FlowTableList` from a query easily via `queryCursorList()` and the `queryTableList()` methods. To see more on these, go to [Flow Lists](../advanced-usage/listbasedqueries.md). - -## Asynchronous Retrieval - -DBFlow provides the very-handy `Transaction` system that allows you to place all calls to the DB in a background queue. Using this system, we recommend placing retrieval queries on this queue to help prevent locking and threading issues when using a database. - -We wrap our queries in a `beginTransactionAsync` block, executing and providing call backs to the method as follows: - -```kotlin -database.beginTransactionAsync { db -> - // body of transaction. Return the value you wish to pass into the Success callback. - (select from TestModel1::class - where TestModel1_Table.name.is("Async")).querySingle(db) - } - .execute( - ready = { transaction -> }, // called when transaction is ready to be executed. - success = { transaction, r -> }, // if successful - error = { transaction, throwable -> }, // any exception thrown is put here - completion = { transaction -> }) // always called success or failure - -// or inverse is supported -(select from TestModel1::class - where TestModel1_Table.name.is("Async")).async { d -> querySingle(d) } - .execute { _, model: TestModel1? -> - - } -``` - -A `ITransaction` simply returns a result, `R` , which could be a query, or a result from multiple queries combined into one result. - -By default the library uses an ordered queue that executes FIFO \(first-in-first-out\) and blocks itself until new `Transaction` are added. Each subsequent call to the `database.beginTransactionAsync` places a new transaction on this queue. - -If you wish to customize and provide a different queue \(or map it to an existing system\), read up on [Transactions](storingdata.md). We also provide constructs such as coroutines and RX `Observables` to map to your team's needs. - diff --git a/usage2/usage/sqlitewrapperlanguage.md b/usage2/usage/sqlitewrapperlanguage.md deleted file mode 100644 index 947c6475440bb463714ee6a87553c1044a71114c..0000000000000000000000000000000000000000 --- a/usage2/usage/sqlitewrapperlanguage.md +++ /dev/null @@ -1,317 +0,0 @@ -# SQLite Query Language - -DBFlow's SQLite wrapper language attempts to make it as easy as possible to write queries, execute statements, and more. - -We will attempt to make this doc comprehensive, but reference the SQLite language for how to formulate queries, as DBFlow follows it as much as possible. - -## SELECT - -The way to query data, `SELECT` are started by: - -```kotlin -select from SomeTable::class -``` - -### Projections - -By default if no parameters are specified in the `select()` query, we use the `*` wildcard qualifier, meaning all columns are returned in the results. - -To specify individual columns, you _must_ use `Property` variables. These get generated when you annotate your `Model` with columns, or created manually. - -```kotlin -select(Player_Table.name, Player_Table.position) - from Player::class -``` - -To specify methods such as `COUNT()` or `SUM()` \(static import on `Method`\): - -```kotlin -select(count(Employee_Table.name), sum(Employee_Table.salary)) - from Employee::class -``` - -Translates to: - -```text -SELECT COUNT(`name`), SUM(`salary`) FROM `Employee`; -``` - -There are more handy methods in `Method`. - -### Operators - -DBFlow supports many kinds of operations. They are formulated into a `OperatorGroup`, which represent a set of `SQLOperator` subclasses combined into a SQLite conditional piece. `Property` translate themselves into `SQLOperator` via their conditional methods such as `eq()`, `lessThan()`, `greaterThan()`, `between()`, `in()`, etc. - -They make it very easy to construct concise and meaningful queries: - -```kotlin -val taxBracketCount = (select(count(Employee_Table.name)) - from Employee::class - where Employee_Table.salary.lessThan(150000) - and Employee_Table.salary.greaterThan(80000)) - .count(database) -``` - -Translates to: - -```text -SELECT COUNT(`name`) FROM `Employee` WHERE `salary`<150000 AND `salary`>80000; -``` - -DBFlow supports `IN`/`NOT IN` and `BETWEEN` as well. - -A more comprehensive list of operations DBFlow supports and what they translate to: - -1. is\(\), eq\(\) -> = -2. isNot\(\), notEq\(\) -> != -3. isNull\(\) -> IS NULL / isNotNull\(\) -> IS NOT NULL -4. like\(\), glob\(\) -5. greaterThan\(\), greaterThanOrEqual\(\), lessThan\(\), lessThanOrEqual\(\) -6. between\(\) -> BETWEEN -7. in\(\), notIn\(\) - -#### Nested Conditions - -To create nested conditions \(in parenthesis more often than not\), just include an `OperatorGroup` as a `SQLOperator` in a query: - -```kotlin -(select from Location::class - where Location_Table.latitude.eq(home.latitude) - and (Location_Table.latitude - - home.latitude) eq 1000L - ) -``` - -Translates to: - -```text -SELECT * FROM `Location` WHERE `latitude`=45.05 AND (`latitude` - 45.05) = 1000 -``` - -#### Nested Queries - -To create a nested query simply include a query as a `Property` via `(query).property`: - -```kotlin -.where((select from(...) where(...)).property) -``` - -This appends a `WHERE (SELECT * FROM {table} )` to the query. - -### JOINS - -For reference, \([JOIN examples](http://www.tutorialspoint.com/sqlite/sqlite_using_joins.htm)\). - -`JOIN` statements are great for combining many-to-many relationships. If your query returns non-table fields and cannot map to an existing object, see about [query models](../advanced-usage/querymodels.md) - -For example we have a table named `Customer` and another named `Reservations`. - -```sql -SELECT FROM `Customer` AS `C` INNER JOIN `Reservations` AS `R` ON `C`.`customerId`=`R`.`customerId` -``` - -```kotlin -// use the different QueryModel (instead of Table) if the result cannot be applied to existing Model classes. -val customers = (select from Customer::class).as("C") - innerJoin().as("R") - on(Customer_Table.customerId - .withTable("C".nameAlias) - eq Reservations_Table.customerId.withTable("R")) - .queryCustomList()) -``` - -The `IProperty.withTable()` method will prepend a `NameAlias` or the `Table` alias to the `IProperty` in the query, convenient for JOIN queries: - -```text -SELECT EMP_ID, NAME, DEPT FROM COMPANY LEFT OUTER JOIN DEPARTMENT - ON COMPANY.ID = DEPARTMENT.EMP_ID -``` - -in DBFlow: - -```kotlin -(select(Company_Table.EMP_ID, Company_Table.DEPT) - from Company::class - leftOuterJoin() - .on(Company_Table.ID.withTable().eq(Department_Table.EMP_ID.withTable())) -) -``` - -### Order By - -```kotlin -// true for 'ASC', false for 'DESC'. ASC is default. -(select from table - orderBy(Customer_Table.customer_id) - - (select from table - orderBy(Customer_Table.customer_id, ascending = true) - orderBy(Customer_Table.name, ascending = false)) -``` - -### Group By - -```kotlin -(select from table) - .groupBy(Customer_Table.customer_id, Customer_Table.customer_name) -``` - -### HAVING - -```kotlin -(select from table) - .groupBy(Customer_Table.customer_id, Customer_Table.customer_name)) - .having(Customer_Table.customer_id.greaterThan(2)) -``` - -### LIMIT + OFFSET - -```kotlin -(select - from table - limit 3) - offset 2) -``` - -## UPDATE - -DBFlow supports two kind of UPDATE: - -1. `ModelAdapter` updates - -2. Query language updates \(for less targeted updates\) - -For simple `UPDATE` for a single or few, concrete set of `Model` stick with \(1\). For powerful multiple `Model` update that can span many rows, use \(2\). In this section we speak on \(2\). **Note:** if using model caching, you'll need to clear it out post an operation from \(2\). - -```sql -UPDATE Ant SET type = 'other' WHERE male = 1 AND type = 'worker'; -``` - -Using DBFlow: - -```kotlin -// Native SQL wrapper -database.beginTransactionAsync { db -> (update() - set Ant_Table.type.eq("other") - where Ant_Table.type.is("worker") - and Ant_Table.isMale.is(true)) - .executeUpdateDelete(db) - }.execute { _, count -> }; // non-UI blocking -``` - -The `Set` part of the `Update` supports different kinds of values: 1. `ContentValues` -> converts to key/value as a `SQLOperator` of `is()`/`eq()` 2. `SQLOperator`, which are grouped together as part of the `SET` statement. - -## DELETE - -`DELETE` queries in DBFlow are similiar to `Update` in that we have two kinds: - -1. `ModelAdapter`deletes. -2. Query language deletes. - -For simple `DELETE` for a single or few, concrete set of `Model` stick with \(1\). For powerful multiple `Model` deletion that can span many rows, use \(2\). In this section we speak on \(2\). **Note:** if using model caching, you'll need to clear it out post an operation from \(2\). - -```kotlin -// Delete a whole table -delete().execute(database) - -// Delete using query -database.beginTransactionAsync { db -> delete() - where DeviceObject_Table.carrier.is("T-MOBILE") - and DeviceObject_Table.device.is("Samsung-Galaxy-S5")) - .executeUpdateDelete(db) - }.execute { _, count -> }; -``` - -## INSERT - -`INSERT` queries in DBFlow are also similiar to `Update` and `Delete` in that we have two kinds: - -1. `Model.insert()` -2. `SQLite.insert()` - -For simple `INSERT` for a single or few, concrete set of `Model` stick with \(1\). For powerful multiple `Model` insertion that can span many rows, use \(2\). In this section we speak on \(2\). **Note:** using model caching, you'll need to clear it out post an operation from \(2\). - -```kotlin -// columns + values via pairs -database.beginTransactionAsync { db -> - (insert(SomeTable_Table.name to "Default", - MSomeTable_Table.phoneNumber to "5555555") - .executeInsert(db) -}.execute() - -// or combine into Operators -database.beginTransactionAsync { db -> - (insert(SomeTable_Table.name eq "Default", - MSomeTable_Table.phoneNumber eq "5555555") - .executeInsert(db) -}.execute() -``` - -`INSERT` supports inserting multiple rows as well. - -```kotlin -// columns + values separately -database.beginTransactionAsync { db -> - (insert(SomeTable_Table.name, SomeTable_Table.phoneNumber) - .values("Default1", "5555555") - .values("Default2", "6666666")) - .executeInsert(db) -}.execute() - -// or combine into Operators -database.beginTransactionAsync { db -> - (insert(SomeTable_Table.name.eq("Default1"), - SomeTable_Table.phoneNumber.eq("5555555")) - .columnValues(SomeTable_Table.name.eq("Default2"), - SomeTable_Table.phoneNumber.eq("6666666"))) - .executeInsert(db) - }.execute() -``` - -## Trigger - -Triggers enable SQLite-level listener operations that perform some operation, modification, or action to run when a specific database event occurs. [See](https://www.sqlite.org/lang_createtrigger.html) for more documentation on its usage. - -```kotlin -createTrigger("SomeTrigger") - .after() insertOn()) - .begin(update() - .set(TestUpdateModel_Table.value.is("Fired")))) - .enable() // enables the trigger if it does not exist, so subsequent calls are OK -``` - -## Case - -The SQLite `CASE` operator is very useful to evaluate a set of conditions and "map" them to a certain value that returns in a SELECT query. - -We have two kinds of case: 1. Simple 2. Searched - -The simple CASE query in DBFlow: - -```kotlin -select(CaseModel_Table.customerId, - CaseModel_Table.firstName, - CaseModel_Table.lastName, - (case(CaseModel_Table.country) - whenever "USA" - then "Domestic" - `else` "Foreign") - .end("CustomerGroup")) - from CaseModel::class -``` - -The CASE is returned as `CustomerGroup` with the valyes of "Domestic" if the country is from the 'USA' otherwise we mark the value as "Foreign". These appear alongside the results set from the SELECT. - -The search CASE is a little more complicated in that each `when()` statement represents a `SQLOperator`, which return a `boolean` expression: - -```kotlin -select(CaseModel_Table.customerId, - CaseModel_Table.firstName, - CaseModel_Table.lastName, - caseWhen(CaseModel_Table.country.eq("USA")) - then "Domestic" - `else` "Foreign") - .end("CustomerGroup")) - from CaseModel:class -``` - diff --git a/usage2/usage/storingdata.md b/usage2/usage/storingdata.md deleted file mode 100644 index effb51fdc95fd483881607b678f07f9bc80e855e..0000000000000000000000000000000000000000 --- a/usage2/usage/storingdata.md +++ /dev/null @@ -1,198 +0,0 @@ -# Storing Data - -DBFlow provide a few mechanisms by which we store data to the database. The difference of options should not provide confusion but rather allow flexibility in what you decide is the best way to store information. - -DB classifies two different kind of DB transactions: - -1. Synchronous -2. Asynchronous - -## Synchronous Storage - -Saving data synchronous on the main thread should be avoided. We can save data synchronously using a synchronous transaction: - -```kotlin -database().executeTransaction { db -> - modelAdapter.save(model, db) - modelAdapter.insert(model, db) - modelAdapter.update(model, db) -} -``` - -_Avoid_ saving large amounts of models outside of a transaction: - -```kotlin -// AVOID -models.forEach { it.save(db) } - -// DO -database().executeTransaction { db -> - modelAdapter().saveAll(models, db) -} -``` - -Doing operations on the main thread can block it if you read and write to the DB on a different thread while accessing DB on the main. Instead, use Async Transactions. - -## Async Transactions - -Transactions are ACID in SQLite, meaning they either occur completely or not at all. Using transactions significantly speed up the time it takes to store. So recommendation you should use transactions whenever you can. - -Async is the preferred method. Transactions, using the `DefaultTransactionManager`, occur on one thread per-database \(to prevent flooding from other DB in your app\) and receive callbacks on the UI. You can override this behavior and roll your own or hook into an existing system, read [here](storingdata.md#custom-transactionmanager). - -Also to use the legacy, priority-based system, read [here](storingdata.md#priority-queue). - -A basic transaction: - -```kotlin - val transaction = database().beginTransactionAsync { db -> - // handle to DB - // return a result, or execute a method that returns a result - }.build() -transaction.execute( - ready = { transaction -> } // perform any setup. - error = { transaction, error -> // handle any exceptions here }, - completion = { transaction -> // called when transaction completes success or fail } - ) { transaction, result -> - // utilize the result returned - -transaction.cancel(); - // attempt to cancel before its run. If it's already ran, this call has no effect. -``` - -The `Success` callback runs post-transaction on the UI thread. The `Error` callback is called on the UI thread if and only if it is specified and an exception occurs, otherwise it is thrown in the `Transaction` as a `RuntimeException`. - -**Note**: all exceptions are caught when specifying the callback. Ensure you handle all errors, otherwise you might miss some problems. - -### ProcessModelTransaction - -`ProcessModelTransaction` allows for more flexibility and for you to easily operate on a set of `Model` in a `Transaction` easily. It holds a list of `Model` by which you provide the modification method in the `Builder`. You can listen for when each are processed inside a normal `Transaction`. - -It is a convenient way to operate on them: - -```kotlin -database.beginTransactionAsync(items.processTransaction { model, db -> - // call some operation on model here - model.save(db) - model.insert(db) // or - model.delete(db) // or - } - .processListener { current, total, modifiedModel -> - // for every model looped through and completes - modelProcessedCount.incrementAndGet() - } - .build()) - .execute() -``` - -You can listen to when operations complete for each model via the `OnModelProcessListener`. These callbacks occur on the UI thread. If you wish to run them on same thread \(great for tests\), set `runProcessListenerOnSameThread()` to `true`. - -### FastStoreModelTransaction - -The `FastStoreModelTransaction` is the quickest, lightest way to store a `List` of `Model` into the database through a `Transaction`. Under the hood it just calls `modelAdapter.saveAll()` or \(insertAll, updateAll, deleteAll\) It comes with some restrictions when compared to `ProcessModelTransaction`: - -1. All `Model` must be from same Table/Model Class. - -2. No progress listening - -3. Can only `save`, `insert`, or `update` the whole list entirely. - -```kotlin -database.beginTransactionAsync(list.fastSave().build()) - .execute() -database.beginTransactionAsync(list.fastInsert().build()) - .execute() -database.beginTransactionAsync(list.fastUpdate().build()) - .execute() -``` - -What it provides: - -1. Reuses `DatabaseStatement`, and other classes where possible. - -2. Opens and closes own `DatabaseStatement` per total execution. - -3. Significant speed bump over `ProcessModelTransaction` at the expense of flexibility. - -### Custom TransactionManager - -If you prefer to roll your own thread-management system or have an existing system you can override the default system included. - -To begin you must implement a `ITransactionQueue`: - -```kotlin -class CustomQueue : ITransactionQueue { - override fun add(transaction: Transaction) { - - } - - override fun cancel(transaction: Transaction) { - - } - - override fun startIfNotAlive() { - } - - override fun cancel(name: String) { - - } - - override fun quit() { - - } -} -``` - -You must provide ways to `add()`, `cancel(Transaction)`, and `startIfNotAlive()`. The other two methods are optional, but recommended. - -`startIfNotAlive()` in the `DefaultTransactionQueue` will start itself \(since it's a thread\). - -Next you can override the `BaseTransactionManager` \(not required, see later\): - -```kotlin -class CustomTransactionManager(databaseDefinition: DBFlowDatabase) - : BaseTransactionManager(CustomTransactionQueue(), databaseDefinition) -``` - -To register it with DBFlow, in your `FlowConfig`, you must: - -```kotlin -FlowManager.init(FlowConfig.Builder(DemoApp.context) - .database(DatabaseConfig( - databaseClass = AppDatabase::class.java, - transactionManagerCreator = { databaseDefinition -> - CustomTransactionManager(databaseDefinition) - }) - .build()) -.build()) -``` - -### Priority Queue - -In versions pre-3.0, DBFlow utilized a `PriorityBlockingQueue` to manage the asynchronous dispatch of `Transaction`. As of 3.0, it has switched to simply a FIFO queue. To keep the legacy way, a `PriorityTransactionQueue` was created. - -As seen in [Custom Transaction Managers](storingdata.md#custom-transactionmanager), we provide a custom instance of the `DefaultTransactionManager` with the `PriorityTransactionQueue` specified: - -```kotlin -FlowManager.init(FlowConfig.builder(context) - .database(DatabaseConfig.Builder(AppDatabase::class.java) - .transactionManagerCreator { db -> - // this will be called once database modules are loaded and created. - DefaultTransactionManager( - PriorityTransactionQueue("DBFlow Priority Queue"), - db) - } - .build()) - .build()) -``` - -What this does is for the specified database \(in this case `AppDatabase`\), now require each `ITransaction` specified for the database should wrap itself around the `PriorityTransactionWrapper`. Otherwise an the `PriorityTransactionQueue` wraps the existing `Transaction` in a `PriorityTransactionWrapper` with normal priority. - -To specify a priority, wrap your original `ITransaction` with a `PriorityTransactionWrapper`: - -```kotlin -database() - .beginTransactionAsync(myTransaction - .withPriority(PriorityTransactionWrapper.PRIORITY_HIGH)) - .execute() -``` - diff --git a/usage2/usage/typeconverters.md b/usage2/usage/typeconverters.md deleted file mode 100644 index 7dafa502394f7df0f11f6c2394d10ca0b5d62f5e..0000000000000000000000000000000000000000 --- a/usage2/usage/typeconverters.md +++ /dev/null @@ -1,67 +0,0 @@ -# TypeConverters - -When building out `Model` classes, you may wish to provide a different type of `@Column` that from the standard supported column types. To recap the standard column types include: - -1. `String`, `char`, `Character` - -2. All numbers types \(primitive + boxed\) - -3. `byte[]`/`Byte` - -4. `Blob` \(DBFlow's version\) - -5. `Date`/`java.sql.Date` - -6. Booleans - -7. `Model` as `@ForeignKey` or `@ColumnMap` - -8. `Calendar` - -9. `BigDecimal` - -10. `UUID` - -`TypeConverter` do _not_ support: - -1. Any Parameterized fields. - -2. `List`, `Map`, etc. Best way to fix this is to create a separate table [relationship](relationships.md) - -3. Conversion from one type-converter to another \(i.e `JSONObject` to `Date`\). The first parameter of `TypeConverter` is the value of the type as if it was a primitive/boxed type. - -4. Conversion from custom type to `Model`, or `Model` to a supported type. - -5. The custom class _must_ map to a non-complex field such as `String`, numbers, `char`/`Character` or `Blob` - -## Define a TypeConverter - -Defining a `TypeConverter` is quick and easy. - -This example creates a `TypeConverter` for a field that is `JSONObject` and converts it to a `String` representation: - -```kotlin -@com.dbflow5.annotation.TypeConverter -class JSONConverter : TypeConverter() { - - override fun getDBValue(model: JSONObject?): String? = model?.toString() - - override fun getModelValue(data: String?): JSONObject? = - try { - JSONObject(data) - } catch (JSONException e) { - // you should consider logging or throwing exception. - null - } - } -} -``` - -Once this is defined, by using the annotation `@TypeConverter`, it is registered automatically across all databases. - -There are cases where you wish to provide multiple `TypeConverter` for same kind of field \(i.e. `Date` with different date formats stored in a DB\). You can override a field's `TypeConverter` locally at the `@Column` level. - -## TypeConverter for specific `@Column` - -In DBFlow, specifying a `TypeConverter` for a `@Column` is as easy as `@Column(typeConverter = JSONConverter::class)`. What it will do is create the converter once for use only when that column is used. -