├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ ├── libs │ ├── arm64-v8a │ │ └── libsqlc-native-driver.so │ ├── armeabi-v7a │ │ └── libsqlc-native-driver.so │ ├── armeabi │ │ └── libsqlc-native-driver.so │ ├── sqlite-native-driver.jar │ ├── x86 │ │ └── libsqlc-native-driver.so │ └── x86_64 │ │ └── libsqlc-native-driver.so │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ ├── com │ │ └── tekartik │ │ │ └── sqflite │ │ │ ├── Constant.java │ │ │ ├── SQLiteOpenHelper.java │ │ │ ├── SqflitePlugin.java │ │ │ ├── SqlCommand.java │ │ │ ├── dev │ │ │ └── Debug.java │ │ │ ├── operation │ │ │ ├── BaseOperation.java │ │ │ ├── BaseReadOperation.java │ │ │ ├── BatchOperation.java │ │ │ ├── ExecuteOperation.java │ │ │ ├── MethodCallOperation.java │ │ │ ├── Operation.java │ │ │ ├── OperationResult.java │ │ │ └── SqlErrorInfo.java │ │ │ └── utils │ │ │ └── SqlUtils.java │ │ └── io │ │ └── liteglue │ │ ├── SQLCode.java │ │ ├── SQLColumnType.java │ │ ├── SQLDatabaseHandle.java │ │ ├── SQLGDatabaseHandle.java │ │ ├── SQLStatementHandle.java │ │ ├── SQLiteConnection.java │ │ ├── SQLiteConnectionFactory.java │ │ ├── SQLiteConnector.java │ │ ├── SQLiteGlueConnection.java │ │ ├── SQLiteNative.java │ │ ├── SQLiteOpenFlags.java │ │ └── SQLiteStatement.java │ └── test │ └── java │ └── com │ └── tekartik │ └── sqflite │ └── SqlCommandTest.java ├── doc ├── dev_tips.md ├── how_to.md ├── opening_asset_db.md ├── opening_db.md ├── troubleshooting.md └── usage_recommendations.md ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android.iml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── yourcompany │ │ │ │ └── sqflite_example │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── example.db │ └── issue_64.db ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── database │ │ └── database.dart │ ├── deprecated_test_page.dart │ ├── exception_test_page.dart │ ├── exp_test_page.dart │ ├── generated │ │ └── i18n.dart │ ├── main.dart │ ├── model │ │ ├── item.dart │ │ ├── main_item.dart │ │ └── test.dart │ ├── open_test_page.dart │ ├── raw_test_page.dart │ ├── slow_test_page.dart │ ├── src │ │ ├── common_import.dart │ │ ├── item_widget.dart │ │ ├── main_item_widget.dart │ │ └── utils.dart │ ├── test_page.dart │ ├── todo_test_page.dart │ └── type_test_page.dart ├── pubspec.yaml └── test │ ├── database_test.dart │ └── model_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Operation.h │ ├── Operation.m │ ├── SqflitePlugin.h │ └── SqflitePlugin.m └── sqflite.podspec ├── lib ├── sqflite.dart ├── sql.dart └── src │ ├── batch.dart │ ├── constant.dart │ ├── database.dart │ ├── database_factory.dart │ ├── exception.dart │ ├── sqflite_impl.dart │ ├── sql_builder.dart │ ├── transaction.dart │ └── utils.dart ├── pubspec.yaml ├── test ├── deprecated_test.dart ├── list_mixin_test.dart ├── sqflite_exception_test.dart ├── sqflite_impl_test.dart ├── sqflite_test.dart ├── sql_builder_test.dart ├── sql_test.dart └── src_database_test.dart └── tool ├── test.sh └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | /.idea/ 4 | .atom/ 5 | .idea 6 | .packages 7 | .pub/ 8 | build/ 9 | ios/.generated/ 10 | packages 11 | pubspec.lock 12 | lib/generated/ 13 | res 14 | 15 | # Directory created by dartdoc 16 | doc/api/ 17 | 18 | # Local folder 19 | .local/ 20 | 21 | # local flutter install (travis) 22 | flutter 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | # Copied from https://github.com/flutter/plugins/blob/master/.travis.yml 4 | sudo: false 5 | addons: 6 | apt: 7 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 8 | sources: 9 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version 10 | - llvm-toolchain-precise # for clang-format-5.0 11 | packages: 12 | - libstdc++6 13 | - fonts-droid 14 | - clang-format-5.0 15 | before_script: 16 | - git clone https://github.com/flutter/flutter.git --depth 1 17 | - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH 18 | - flutter doctor 19 | script: 20 | - tool/travis.sh 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0 2 | 3 | * add `getDatabasesPath` to use as the base location to create a database 4 | * Warning: database are now single instance by default (based on `path`), to use the 5 | old behavior use `singleInstance = false` when opening a database 6 | * dart2 stable support 7 | 8 | ## 0.10.0 9 | 10 | * Preparing for 1.0 11 | * Remove deprecated methods (re-entrant transactions) 12 | * Add `Transaction.batch` 13 | * Show developer warning to prevent deadlock 14 | 15 | ## 0.9.0 16 | 17 | * Support for in-memory database (`:memory:` path) 18 | * Support for single instance 19 | * new database factory for handling the new options 20 | 21 | ## 0.8.9 22 | 23 | * Upgrade to sdk 27 24 | 25 | ## 0.8.8 26 | 27 | * Allow testing for constraint exception 28 | 29 | ## 0.8.6 30 | 31 | * better sql error report 32 | * catch android native errors 33 | * no longer print an error when deleting a database fails 34 | 35 | ## 0.8.4 36 | 37 | * Add read-only support using `openReadOnlyDatabase` 38 | 39 | ## 0.8.3 40 | 41 | * Allow running a batch during a transaction using `Transaction.applyBatch` 42 | * Restore `Batch.commit` to use outside a transaction 43 | 44 | ## 0.8.2 45 | 46 | * Although already in a transaction, allow creating nested transactions during open 47 | 48 | ## 0.8.1 49 | 50 | * New `Transaction` mechanism not using Zone (old one still supported for now) 51 | * Start using `Batch.apply` instead of `Batch.commit` 52 | * Deprecate `Database.inTransaction` and `Database.synchronized` so that Zones are not used anymore 53 | 54 | ## 0.7.1 55 | 56 | * add `Batch.query`, `Batch.rawQuery` and `Batch.execute` 57 | * pack query result as colums/rows instead of List 58 | 59 | ## 0.7.0 60 | 61 | * Add support for `--preview-dart-2` 62 | 63 | ## 0.6.2+1 64 | 65 | * Add longer description to pubspec.yaml 66 | 67 | ## 0.6.2 68 | 69 | * Fix travis warning 70 | 71 | ## 0.6.1 72 | 73 | * Add Flutter SDK constraint to pubspec.yaml 74 | 75 | ## 0.6.0 76 | 77 | * add support for `onConfigure` to allow for database configuration 78 | 79 | ## 0.5.0 80 | 81 | * Escape table and column name when needed in insert/update/query/delete 82 | * Export ConflictAlgorithm, escapeName, unescapeName in new sql.dart 83 | 84 | ## 0.4.0 85 | 86 | * Add support for Batch (insert/update/delete) 87 | 88 | ## 0.3.1 89 | 90 | * Remove temp concurrency experiment 91 | 92 | ## 0.3.0 93 | 94 | 2018/01/04 95 | 96 | * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 97 | 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in 98 | order to use this version of the plugin. Instructions can be found 99 | [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). 100 | 101 | ## 0.2.4 102 | 103 | * Dependency on synchronized updated to >=1.1.0 104 | 105 | ## 0.2.3 106 | 107 | * Make Android sends the reponse in the same thread then the caller to prevent unexpected behavior when an error occured 108 | 109 | ## 0.2.2 110 | 111 | * Fix unchecked warning on Android 112 | 113 | ## 0.2.0 114 | 115 | * Use NSOperationQueue for all db operation on iOS 116 | * Use ThreadHandler for all db operation on Android 117 | 118 | ## 0.0.3 119 | 120 | * Add exception handling 121 | 122 | ## 0.0.2 123 | 124 | * Add sqlite helpers based on Razvan Lung suggestions 125 | 126 | ## 0.0.1 127 | 128 | * Initial experimentation 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Alexandre Roux Tekartik. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqflite 2 | 3 | SQLite plugin for [Flutter](https://flutter.io). 4 | Supports both iOS and Android. 5 | 6 | * Support transactions and batches 7 | * Automatic version managment during open 8 | * Helpers for insert/query/update/delete queries 9 | * DB operation executed in a background thread on iOS and Android 10 | 11 | ## Getting Started 12 | 13 | In your flutter project add the dependency: 14 | 15 | ```yml 16 | dependencies: 17 | ... 18 |  sqflite: any 19 | ``` 20 | 21 | For help getting started with Flutter, view the online 22 | [documentation](https://flutter.io/). 23 | 24 | ## Usage example 25 | 26 | Import `sqflite.dart` 27 | 28 | ```dart 29 | import 'package:sqflite/sqflite.dart'; 30 | ``` 31 | 32 | ### Raw SQL queries 33 | 34 | Demo code to perform Raw SQL queries 35 | 36 | ```dart 37 | // Get a location using getDatabasesPath 38 | var databasesPath = await getDatabasesPath(); 39 | String path = join(databasesPath, "demo.db"); 40 | 41 | // Delete the database 42 | await deleteDatabase(path); 43 | 44 | // open the database 45 | Database database = await openDatabase(path, version: 1, 46 | onCreate: (Database db, int version) async { 47 | // When creating the db, create the table 48 | await db.execute( 49 | "CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)"); 50 | }); 51 | 52 | // Insert some records in a transaction 53 | await database.transaction((txn) async { 54 | int id1 = await txn.rawInsert( 55 | 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)'); 56 | print("inserted1: $id1"); 57 | int id2 = await txn.rawInsert( 58 | 'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)', 59 | ["another name", 12345678, 3.1416]); 60 | print("inserted2: $id2"); 61 | }); 62 | 63 | // Update some record 64 | int count = await database.rawUpdate( 65 | 'UPDATE Test SET name = ?, VALUE = ? WHERE name = ?', 66 | ["updated name", "9876", "some name"]); 67 | print("updated: $count"); 68 | 69 | // Get the records 70 | List list = await database.rawQuery('SELECT * FROM Test'); 71 | List expectedList = [ 72 | {"name": "updated name", "id": 1, "value": 9876, "num": 456.789}, 73 | {"name": "another name", "id": 2, "value": 12345678, "num": 3.1416} 74 | ]; 75 | print(list); 76 | print(expectedList); 77 | assert(const DeepCollectionEquality().equals(list, expectedList)); 78 | 79 | // Count the records 80 | count = Sqflite 81 | .firstIntValue(await database.rawQuery("SELECT COUNT(*) FROM Test")); 82 | assert(count == 2); 83 | 84 | // Delete a record 85 | count = await database 86 | .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']); 87 | assert(count == 1); 88 | 89 | // Close the database 90 | await database.close(); 91 | ``` 92 | 93 | ### SQL helpers 94 | 95 | Example using the helpers 96 | 97 | ```dart 98 | final String tableTodo = "todo"; 99 | final String columnId = "_id"; 100 | final String columnTitle = "title"; 101 | final String columnDone = "done"; 102 | 103 | class Todo { 104 | int id; 105 | String title; 106 | bool done; 107 | 108 | Map toMap() { 109 | Map map = {columnTitle: title, columnDone: done == true ? 1 : 0}; 110 | if (id != null) { 111 | map[columnId] = id; 112 | } 113 | return map; 114 | } 115 | 116 | Todo(); 117 | 118 | Todo.fromMap(Map map) { 119 | id = map[columnId]; 120 | title = map[columnTitle]; 121 | done = map[columnDone] == 1; 122 | } 123 | } 124 | 125 | class TodoProvider { 126 | Database db; 127 | 128 | Future open(String path) async { 129 | db = await openDatabase(path, version: 1, 130 | onCreate: (Database db, int version) async { 131 | await db.execute(''' 132 | create table $tableTodo ( 133 | $columnId integer primary key autoincrement, 134 | $columnTitle text not null, 135 | $columnDone integer not null) 136 | '''); 137 | }); 138 | } 139 | 140 | Future insert(Todo todo) async { 141 | todo.id = await db.insert(tableTodo, todo.toMap()); 142 | return todo; 143 | } 144 | 145 | Future getTodo(int id) async { 146 | List maps = await db.query(tableTodo, 147 | columns: [columnId, columnDone, columnTitle], 148 | where: "$columnId = ?", 149 | whereArgs: [id]); 150 | if (maps.length > 0) { 151 | return new Todo.fromMap(maps.first); 152 | } 153 | return null; 154 | } 155 | 156 | Future delete(int id) async { 157 | return await db.delete(tableTodo, where: "$columnId = ?", whereArgs: [id]); 158 | } 159 | 160 | Future update(Todo todo) async { 161 | return await db.update(tableTodo, todo.toMap(), 162 | where: "$columnId = ?", whereArgs: [todo.id]); 163 | } 164 | 165 | Future close() async => db.close(); 166 | } 167 | ``` 168 | 169 | ### Transaction 170 | 171 | Don't use the database but only use the Transaction object in a transaction 172 | to access the database 173 | 174 | ```dart 175 | await database.transaction((txn) async { 176 | // Ok 177 | await txn.execute("CREATE TABLE Test1 (id INTEGER PRIMARY KEY)"); 178 | 179 | // DON'T use the database object in a transaction 180 | // this will deadlock! 181 | await database.execute("CREATE TABLE Test2 (id INTEGER PRIMARY KEY)"); 182 | }); 183 | ``` 184 | 185 | ### Batch support 186 | 187 | To avoid ping-pong between dart and native code, you can use `Batch`: 188 | 189 | ```dart 190 | batch = db.batch(); 191 | batch.insert("Test", {"name": "item"}); 192 | batch.update("Test", {"name": "new_item"}, where: "name = ?", whereArgs: ["item"]); 193 | batch.delete("Test", where: "name = ?", whereArgs: ["item"]); 194 | results = await batch.commit(); 195 | ``` 196 | 197 | Getting the result for each operation has a cost (id for insertion and number of changes for 198 | update and delete), especially on Android where an extra SQL request is executed. 199 | If you don't care about the result and worry about performance in big batches, you can use 200 | 201 | ```dart 202 | await batch.commit(noResult: true); 203 | ``` 204 | 205 | Warning, during a transaction, the batch won't be commited until the transaction is commited 206 | 207 | ```dart 208 | await database.transaction((txn) async { 209 | var batch = txn.batch(); 210 | 211 | // ... 212 | 213 | // commit but the actual commit will happen when the transaction is commited 214 | // however the data is available in this transaction 215 | await batch.commit(); 216 | 217 | // ... 218 | }); 219 | ``` 220 | 221 | ## Table and column names 222 | 223 | In general it is better to avoid using SQLite keywords for entity names. If any of the following 224 | name is used: 225 | 226 | "add","all","alter","and","as","autoincrement","between","case","check","collate","commit","constraint","create","default","deferrable","delete","distinct","drop","else","escape","except","exists","foreign","from","group","having","if","in","index","insert","intersect","into","is","isnull","join","limit","not","notnull","null","on","or","order","primary","references","select","set","table","then","to","transaction","union","unique","update","using","values","when","where" 227 | 228 | the helper will *escape* the name i.e. 229 | 230 | ```dart 231 | db.query("table") 232 | ``` 233 | will be equivalent to manually adding double-quote around the table name (confusingly here named `table`) 234 | 235 | ```dart 236 | db.rawQuery('SELECT * FROM "table"'); 237 | ``` 238 | 239 | However in any other raw statement (including `orderBy`, `where`, `groupBy`), make sure to escape the name 240 | properly using double quote. For example see below where the column name `group` is not escaped in the columns 241 | argument, but is escaped in the `where` argument. 242 | 243 | ```dart 244 | db.query("table", columns: ["group"], where: '"group" = ?', whereArgs: ["my_group"]); 245 | ``` 246 | 247 | ## Supported SQLite types 248 | 249 | No validity check is done on values yet so please avoid non supported types. DateTime is not 250 | a supported SQL type (https://www.sqlite.org/datatype3.html). Personally I store them as 251 | int (millisSinceEpoch) or string (iso8601) 252 | 253 | ### INTEGER 254 | 255 | * Dart type: `int` 256 | * Supported values: from -2^63 to 2^63 - 1 257 | 258 | ### REAL 259 | 260 | * Dart type: `num` 261 | 262 | ### TEXT 263 | 264 | * Dart type: `String` 265 | 266 | ### BLOB 267 | 268 | * Dart type: `Uint8List` 269 | * Dart type `List` is supported but not recommended (slow conversion) 270 | 271 | ## Current issues 272 | 273 | * Due to the way transaction works in SQLite (threads), concurrent read and write transaction are not supported. 274 | All calls are currently synchronized and transactions block are exclusive. I thought that a basic way to support 275 | concurrent access is to open a database multiple times but it only works on iOS as Android reuses the same database object. 276 | I also thought a native thread could be a potential future solution however on android accessing the database in another 277 | thread is blocked while in a transaction... 278 | * Currently INTEGER are limited to -2^63 to 2^63 - 1 (although Android supports bigger ones) 279 | 280 | ## More 281 | 282 | * [How to](https://github.com/tekartik/sqflite/blob/master/doc/how_to.md) guide 283 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false 4 | implicit-dynamic: false 5 | 6 | linter: 7 | rules: 8 | - iterable_contains_unrelated_type 9 | - list_remove_unrelated_type 10 | - test_types_in_equals 11 | - unrelated_type_equality_checks 12 | - valid_regexps 13 | - annotate_overrides 14 | - hash_and_equals 15 | - prefer_is_not_empty 16 | - avoid_empty_else 17 | - cancel_subscriptions 18 | - close_sinks 19 | - always_declare_return_types 20 | - camel_case_types 21 | - empty_constructor_bodies 22 | - annotate_overrides 23 | - avoid_init_to_null 24 | - constant_identifier_names 25 | - one_member_abstracts 26 | - slash_for_doc_comments 27 | - sort_constructors_first 28 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | 10 | /gradle 11 | /gradlew 12 | /gradlew.bat 13 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.tekartik.sqflite' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.1.2' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | allprojects { 23 | gradle.projectsEvaluated { 24 | tasks.withType(JavaCompile) { 25 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 26 | } 27 | } 28 | } 29 | 30 | apply plugin: 'com.android.library' 31 | 32 | android { 33 | compileSdkVersion 27 34 | 35 | defaultConfig { 36 | minSdkVersion 16 37 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 38 | } 39 | lintOptions { 40 | disable 'InvalidPackage' 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation fileTree(dir: 'libs', include: '*.jar') 46 | implementation 'net.zetetic:android-database-sqlcipher:3.5.9' 47 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'sqflite' 2 | -------------------------------------------------------------------------------- /android/src/libs/arm64-v8a/libsqlc-native-driver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/arm64-v8a/libsqlc-native-driver.so -------------------------------------------------------------------------------- /android/src/libs/armeabi-v7a/libsqlc-native-driver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/armeabi-v7a/libsqlc-native-driver.so -------------------------------------------------------------------------------- /android/src/libs/armeabi/libsqlc-native-driver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/armeabi/libsqlc-native-driver.so -------------------------------------------------------------------------------- /android/src/libs/sqlite-native-driver.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/sqlite-native-driver.jar -------------------------------------------------------------------------------- /android/src/libs/x86/libsqlc-native-driver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/x86/libsqlc-native-driver.so -------------------------------------------------------------------------------- /android/src/libs/x86_64/libsqlc-native-driver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/android/src/libs/x86_64/libsqlc-native-driver.so -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/Constant.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite; 2 | 3 | /** 4 | * Constants between dart & Java world 5 | */ 6 | 7 | public class Constant { 8 | 9 | static final public String METHOD_GET_PLATFORM_VERSION = "getPlatformVersion"; 10 | static final public String METHOD_GET_DATABASES_PATH = "getDatabasesPath"; 11 | static final public String METHOD_DEBUG_MODE = "debugMode"; 12 | static final public String METHOD_OPTIONS = "options"; 13 | static final public String METHOD_OPEN_DATABASE = "openDatabase"; 14 | static final public String METHOD_CLOSE_DATABASE = "closeDatabase"; 15 | static final public String METHOD_INSERT = "insert"; 16 | static final public String METHOD_EXECUTE = "execute"; 17 | static final public String METHOD_QUERY = "query"; 18 | static final public String METHOD_UPDATE = "update"; 19 | static final public String METHOD_BATCH = "batch"; 20 | 21 | static final String PARAM_ID = "id"; 22 | static final String PARAM_PATH = "path"; 23 | // when opening a database 24 | static final String PARAM_READ_ONLY = "readOnly"; // boolean 25 | static final String PARAM_PASSWORD = "password"; 26 | 27 | static final String PARAM_QUERY_AS_MAP_LIST = "queryAsMapList"; // boolean 28 | 29 | public static final String PARAM_SQL = "sql"; 30 | public static final String PARAM_SQL_ARGUMENTS = "arguments"; 31 | public static final String PARAM_NO_RESULT = "noResult"; 32 | 33 | // in batch 34 | static final String PARAM_OPERATIONS = "operations"; 35 | // in each operation 36 | public static final String PARAM_METHOD = "method"; 37 | 38 | static final String SQLITE_ERROR = "sqlite_error"; // code 39 | static final String ERROR_BAD_PARAM = "bad_param"; // internal only 40 | static final String ERROR_OPEN_FAILED = "open_failed"; // msg 41 | static final String ERROR_DATABASE_CLOSED = "database_closed"; // msg 42 | 43 | // memory database path 44 | static final String MEMORY_DATABASE_PATH = ":memory:"; 45 | 46 | // android log tag 47 | static public String TAG = "Sqflite"; 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/SQLiteOpenHelper.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite; 2 | 3 | 4 | import android.content.Context; 5 | 6 | import net.sqlcipher.DatabaseErrorHandler; 7 | import net.sqlcipher.database.SQLiteDatabase; 8 | import net.sqlcipher.database.SQLiteException; 9 | 10 | /** 11 | * A helper class to manage database creation and version management. 12 | *

13 | * Copy from the system removing onCreate/onUpgrade/onDowngrade 14 | *

15 | *

You create a subclass implementing 16 | * optionally {@link #onOpen}, and this class takes care of opening the database 17 | * if it exists, creating it if it does not, and upgrading it as necessary. 18 | * Transactions are used to make sure the database is always in a sensible state. 19 | *

20 | *

This class makes it easy for {@link android.content.ContentProvider} 21 | * implementations to defer opening and upgrading the database until first use, 22 | * to avoid blocking application startup with long-running database upgrades. 23 | *

24 | *

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

26 | *

27 | *

Note: this class assumes 28 | * monotonically increasing version numbers for upgrades.

29 | */ 30 | public abstract class SQLiteOpenHelper { 31 | private static final String TAG = android.database.sqlite.SQLiteOpenHelper.class.getSimpleName(); 32 | 33 | // When true, getReadableDatabase returns a read-only database if it is just being opened. 34 | // The database handle is reopened in read/write mode when getWritableDatabase is called. 35 | // We leave this behavior disabled in production because it is inefficient and breaks 36 | // many applications. For debugging purposes it can be useful to turn on strict 37 | // read-only semantics to catch applications that call getReadableDatabase when they really 38 | // wanted getWritableDatabase. 39 | private static final boolean DEBUG_STRICT_READONLY = false; 40 | 41 | private final Context mContext; 42 | private final String mName; 43 | private final SQLiteDatabase.CursorFactory mFactory; 44 | private final String password; 45 | 46 | private SQLiteDatabase mDatabase; 47 | private boolean mIsInitializing; 48 | private boolean mEnableWriteAheadLogging; 49 | private final DatabaseErrorHandler mErrorHandler; 50 | 51 | /** 52 | * Create a helper object to create, open, and/or manage a database. 53 | * This method always returns very quickly. The database is not actually 54 | * created or opened until one of {@link #getWritableDatabase} or 55 | * {@link #getReadableDatabase} is called. 56 | * 57 | * @param context to use to open or create the database 58 | * @param name of the database file 59 | * @param password 60 | * @param factory to use for creating cursor objects, or null for the default 61 | */ 62 | public SQLiteOpenHelper(Context context, String name, String password, SQLiteDatabase.CursorFactory factory) { 63 | this(context, name, password, factory, null); 64 | } 65 | 66 | /** 67 | * Create a helper object to create, open, and/or manage a database. 68 | * The database is not actually created or opened until one of 69 | * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. 70 | *

71 | *

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

73 | * 74 | * @param context to use to open or create the database 75 | * @param name of the database file 76 | * @param password 77 | * @param factory to use for creating cursor objects, or null for the default 78 | * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database 79 | */ 80 | public SQLiteOpenHelper(Context context, String name, String password, SQLiteDatabase.CursorFactory factory, 81 | DatabaseErrorHandler errorHandler) { 82 | mContext = context; 83 | mName = name == null ? "base.db" : name; 84 | mFactory = factory; 85 | this.password = password; 86 | mErrorHandler = errorHandler; 87 | } 88 | 89 | /** 90 | * Return the name of the SQLite database being opened, as given to 91 | * the constructor. 92 | */ 93 | public String getDatabaseName() { 94 | return mName; 95 | } 96 | 97 | /** 98 | * Create and/or open a database that will be used for reading and writing. 99 | * The first time this is called, the database will be opened and 100 | * {@link #onOpen} will be 101 | * called. 102 | *

103 | *

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

108 | *

109 | *

Database upgrade may take a long time, you 110 | * should not call this method from the application main thread, including 111 | * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. 112 | * 113 | * @return a read/write database object valid until {@link #close} is called 114 | * @throws SQLiteException if the database cannot be opened for writing 115 | */ 116 | protected SQLiteDatabase getWritableDatabase() { 117 | synchronized (this) { 118 | return getDatabaseLocked(true); 119 | } 120 | } 121 | 122 | /** 123 | * Create and/or open a database. This will be the same object returned by 124 | * {@link #getWritableDatabase} unless some problem, such as a full disk, 125 | * requires the database to be opened read-only. In that case, a read-only 126 | * database object will be returned. If the problem is fixed, a future call 127 | * to {@link #getWritableDatabase} may succeed, in which case the read-only 128 | * database object will be closed and the read/write object will be returned 129 | * in the future. 130 | *

131 | *

Like {@link #getWritableDatabase}, this method may 132 | * take a long time to return, so you should not call it from the 133 | * application main thread, including from 134 | * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. 135 | * 136 | * @return a database object valid until {@link #getWritableDatabase} 137 | * or {@link #close} is called. 138 | * @throws SQLiteException if the database cannot be opened 139 | */ 140 | public SQLiteDatabase getReadableDatabase() { 141 | synchronized (this) { 142 | return getDatabaseLocked(false); 143 | } 144 | } 145 | 146 | private SQLiteDatabase getDatabaseLocked(boolean writable) { 147 | if (mDatabase != null) { 148 | if (!mDatabase.isOpen()) { 149 | // Darn! The user closed the database by calling mDatabase.close(). 150 | mDatabase = null; 151 | } else if (!writable || !mDatabase.isReadOnly()) { 152 | // The database is already open for business. 153 | return mDatabase; 154 | } 155 | } 156 | 157 | if (mIsInitializing) { 158 | throw new IllegalStateException("getDatabase called recursively"); 159 | } 160 | 161 | SQLiteDatabase db = mDatabase; 162 | try { 163 | mIsInitializing = true; 164 | 165 | if (db != null) { 166 | // Ok 167 | } else { 168 | db = SQLiteDatabase.openOrCreateDatabase(mContext.getDatabasePath(mName), password, mFactory, null, mErrorHandler); 169 | } 170 | 171 | onConfigure(db); 172 | 173 | onOpen(db); 174 | 175 | mDatabase = db; 176 | return db; 177 | } finally { 178 | mIsInitializing = false; 179 | if (db != null && db != mDatabase) { 180 | db.close(); 181 | } 182 | } 183 | } 184 | 185 | /** 186 | * Close any open database object. 187 | */ 188 | public synchronized void close() { 189 | if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); 190 | 191 | if (mDatabase != null && mDatabase.isOpen()) { 192 | mDatabase.close(); 193 | mDatabase = null; 194 | } 195 | } 196 | 197 | //todo add this: 198 | // PRAGMA foreign_keys = ON; 199 | // PRAGMA journal_mode=WAL; 200 | 201 | /** 202 | * Called when the database connection is being configured, to enable features 203 | * such as write-ahead logging or foreign key support. 204 | *

205 | * This method is called before {@link #onOpen} are called. It should not modify 206 | * the database except to configure the database connection as required. 207 | *

208 | * This method should only call methods that configure the parameters of the 209 | * database connection, such as {@link SQLiteDatabase#setLocale}, 210 | * {@link SQLiteDatabase#setMaximumSize}, 211 | * or executing PRAGMA statements. 212 | *

213 | * 214 | * @param db The database. 215 | */ 216 | public void onConfigure(SQLiteDatabase db) { 217 | } 218 | 219 | /** 220 | * Called when the database has been opened. The implementation 221 | * should check {@link SQLiteDatabase#isReadOnly} before updating the 222 | * database. 223 | *

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

229 | * 230 | * @param db The database. 231 | */ 232 | public void onOpen(SQLiteDatabase db) { 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/SqlCommand.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import static com.tekartik.sqflite.Constant.TAG; 12 | import static com.tekartik.sqflite.dev.Debug.EXTRA_LOGV; 13 | 14 | public class SqlCommand { 15 | public String getSql() { 16 | return sql; 17 | } 18 | 19 | final private String sql; 20 | final private List rawArguments; 21 | 22 | 23 | // Handle list of int as byte[] 24 | static private Object toValue(Object value) { 25 | if (value == null) { 26 | return null; 27 | } else { 28 | if (EXTRA_LOGV) { 29 | Log.d(TAG, "arg " + value.getClass().getCanonicalName() + " " + toString(value)); 30 | } 31 | // Assume a list is a blog 32 | if (value instanceof List) { 33 | @SuppressWarnings("unchecked") 34 | List list = (List) value; 35 | byte[] blob = new byte[list.size()]; 36 | for (int i = 0; i < list.size(); i++) { 37 | blob[i] = (byte) (int) list.get(i); 38 | } 39 | value = blob; 40 | 41 | } 42 | if (EXTRA_LOGV) { 43 | Log.d(TAG, "arg " + value.getClass().getCanonicalName() + " " + toString(value)); 44 | } 45 | return value; 46 | } 47 | } 48 | 49 | public SqlCommand(String sql, List rawArguments) { 50 | this.sql = sql; 51 | if (rawArguments == null) { 52 | rawArguments = new ArrayList<>(); 53 | } 54 | this.rawArguments = rawArguments; 55 | 56 | } 57 | 58 | // Only sanitize if the parameter count matches the argument count 59 | // For integer value replace ? with the actual value directly 60 | // to workaround an issue with references 61 | public SqlCommand sanitizeForQuery() { 62 | if (rawArguments.size() == 0) { 63 | return this; 64 | } 65 | StringBuilder sanitizeSqlSb = new StringBuilder(); 66 | List sanitizeArguments = new ArrayList<>(); 67 | int count = 0; 68 | int argumentIndex = 0; 69 | int sqlLength = sql.length(); 70 | for (int i = 0; i < sqlLength; i++) { 71 | char ch = sql.charAt(i); 72 | if (ch == '?') { 73 | count++; 74 | // no match, return the same 75 | if (argumentIndex >= rawArguments.size()) { 76 | return this; 77 | } 78 | Object argument = rawArguments.get(argumentIndex++); 79 | if (argument instanceof Integer || argument instanceof Long) { 80 | sanitizeSqlSb.append(argument.toString()); 81 | continue; 82 | } else { 83 | // Let the other args as is 84 | sanitizeArguments.add(argument); 85 | } 86 | } 87 | // Simply append the existing 88 | sanitizeSqlSb.append(ch); 89 | } 90 | // no match (there might be an extra ? somwhere), return the same 91 | if (count != rawArguments.size()) { 92 | return this; 93 | } 94 | return new SqlCommand(sanitizeSqlSb.toString(), sanitizeArguments); 95 | } 96 | 97 | 98 | // Query only accept string arguments 99 | // so should not have byte[] 100 | private String[] getQuerySqlArguments(List rawArguments) { 101 | return getStringQuerySqlArguments(rawArguments).toArray(new String[0]); 102 | } 103 | 104 | private Object[] getSqlArguments(List rawArguments) { 105 | List fixedArguments = new ArrayList<>(); 106 | if (rawArguments != null) { 107 | for (Object rawArgument : rawArguments) { 108 | fixedArguments.add(toValue(rawArgument)); 109 | } 110 | } 111 | return fixedArguments.toArray(new Object[0]); 112 | } 113 | 114 | 115 | // Query only accept string arguments 116 | private List getStringQuerySqlArguments(List rawArguments) { 117 | List stringArguments = new ArrayList<>(); 118 | if (rawArguments != null) { 119 | for (Object rawArgument : rawArguments) { 120 | stringArguments.add(toString(rawArgument)); 121 | } 122 | } 123 | return stringArguments; 124 | } 125 | 126 | 127 | // Convert a value to a string 128 | // especially byte[] 129 | static private String toString(Object value) { 130 | if (value == null) { 131 | return null; 132 | } else if (value instanceof byte[]) { 133 | List list = new ArrayList<>(); 134 | for (byte _byte : (byte[]) value) { 135 | list.add((int) _byte); 136 | } 137 | return list.toString(); 138 | } else if (value instanceof Map) { 139 | @SuppressWarnings("unchecked") 140 | Map mapValue = (Map) value; 141 | return fixMap(mapValue).toString(); 142 | } else { 143 | return value.toString(); 144 | } 145 | } 146 | 147 | 148 | static private Map fixMap(Map map) { 149 | Map newMap = new HashMap<>(); 150 | for (Map.Entry entry : map.entrySet()) { 151 | Object value = entry.getValue(); 152 | if (value instanceof Map) { 153 | @SuppressWarnings("unchecked") 154 | Map mapValue = (Map) value; 155 | value = fixMap(mapValue); 156 | } else { 157 | value = toString(value); 158 | } 159 | newMap.put(toString(entry.getKey()), value); 160 | } 161 | return newMap; 162 | } 163 | 164 | @Override 165 | public String toString() { 166 | return sql + ((rawArguments == null || rawArguments.isEmpty()) ? "" : (" " + getStringQuerySqlArguments(rawArguments))); 167 | } 168 | 169 | // As expected by execSQL 170 | public Object[] getSqlArguments() { 171 | return getSqlArguments(rawArguments); 172 | } 173 | 174 | public String[] getQuerySqlArguments() { 175 | return getQuerySqlArguments(rawArguments); 176 | } 177 | 178 | public List getRawSqlArguments() { 179 | return rawArguments; 180 | } 181 | 182 | @Override 183 | public int hashCode() { 184 | return sql != null ? sql.hashCode() : 0; 185 | } 186 | 187 | @Override 188 | public boolean equals(Object obj) { 189 | if (obj instanceof SqlCommand) { 190 | SqlCommand o = (SqlCommand) obj; 191 | if (sql != null) { 192 | if (!sql.equals(o.sql)) { 193 | return false; 194 | } 195 | } else { 196 | if (o.sql != null) { 197 | return false; 198 | } 199 | } 200 | 201 | if (rawArguments.size() != o.rawArguments.size()) { 202 | return false; 203 | } 204 | for (int i = 0; i < rawArguments.size(); i++) { 205 | // special blob handling 206 | if (rawArguments.get(i) instanceof byte[] && o.rawArguments.get(i) instanceof byte[]) { 207 | if (!Arrays.equals((byte[]) rawArguments.get(i), (byte[]) o.rawArguments.get(i))) { 208 | return false; 209 | } 210 | } else { 211 | if (!rawArguments.get(i).equals(o.rawArguments.get(i))) { 212 | return false; 213 | } 214 | } 215 | } 216 | return true; 217 | } 218 | return false; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/dev/Debug.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.dev; 2 | 3 | import android.util.Log; 4 | 5 | import static android.content.ContentValues.TAG; 6 | 7 | /** 8 | * Created by alex on 09/01/18. 9 | */ 10 | 11 | public class Debug { 12 | 13 | // Log flags 14 | public static boolean LOGV = false; 15 | public static boolean _EXTRA_LOGV = false; // to set to true for type debugging 16 | static public boolean EXTRA_LOGV = false; // to set to true for type debugging 17 | 18 | // Deprecated to prevent usage 19 | @Deprecated 20 | public static void devLog(String tag, String message) { 21 | Log.d(TAG, message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/BaseOperation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import static com.tekartik.sqflite.Constant.PARAM_NO_RESULT; 4 | 5 | /** 6 | * Created by alex on 09/01/18. 7 | */ 8 | 9 | public abstract class BaseOperation extends BaseReadOperation { 10 | 11 | @Override 12 | public boolean getNoResult() { 13 | return Boolean.TRUE.equals(getArgument(PARAM_NO_RESULT)); 14 | } 15 | 16 | // We actually have an inner object that does the implementation 17 | protected abstract OperationResult getResult(); 18 | 19 | @Override 20 | public void success(Object results) { 21 | getResult().success(results); 22 | } 23 | 24 | @Override 25 | public void error(String errorCode, String errorMessage, Object data) { 26 | getResult().error(errorCode, errorMessage, data); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/BaseReadOperation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import com.tekartik.sqflite.SqlCommand; 4 | 5 | import java.util.List; 6 | 7 | import static com.tekartik.sqflite.Constant.PARAM_NO_RESULT; 8 | import static com.tekartik.sqflite.Constant.PARAM_SQL; 9 | import static com.tekartik.sqflite.Constant.PARAM_SQL_ARGUMENTS; 10 | 11 | /** 12 | * Created by alex on 09/01/18. 13 | */ 14 | 15 | public abstract class BaseReadOperation implements Operation { 16 | private String getSql() { 17 | return getArgument(PARAM_SQL); 18 | } 19 | 20 | private List getSqlArguments() { 21 | return getArgument(PARAM_SQL_ARGUMENTS); 22 | } 23 | 24 | public SqlCommand getSqlCommand() { 25 | return new SqlCommand(getSql(), getSqlArguments()); 26 | } 27 | 28 | @Override 29 | public boolean getNoResult() { 30 | return Boolean.TRUE.equals(getArgument(PARAM_NO_RESULT)); 31 | } 32 | 33 | // We actually have an inner object that does the implementation 34 | protected abstract OperationResult getResult(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/BatchOperation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import io.flutter.plugin.common.MethodChannel; 7 | 8 | import static com.tekartik.sqflite.Constant.PARAM_METHOD; 9 | 10 | /** 11 | * Created by alex on 09/01/18. 12 | */ 13 | 14 | public class BatchOperation extends BaseOperation { 15 | final Map map; 16 | final BatchOperationResult result = new BatchOperationResult(); 17 | final boolean noResult; 18 | 19 | class BatchOperationResult implements OperationResult { 20 | // success 21 | Object results; 22 | 23 | // error 24 | String errorCode; 25 | String errorMessage; 26 | Object data; 27 | 28 | @Override 29 | public void success(Object results) { 30 | this.results = results; 31 | } 32 | 33 | @Override 34 | public void error(String errorCode, String errorMessage, Object data) { 35 | this.errorCode = errorCode; 36 | this.errorMessage = errorMessage; 37 | this.data = data; 38 | } 39 | } 40 | 41 | public BatchOperation(Map map, boolean noResult) { 42 | this.map = map; 43 | this.noResult = noResult; 44 | } 45 | 46 | @Override 47 | public String getMethod() { 48 | return (String) map.get(PARAM_METHOD); 49 | } 50 | 51 | @SuppressWarnings("unchecked") 52 | @Override 53 | public T getArgument(String key) { 54 | return (T) map.get(key); 55 | } 56 | 57 | @Override 58 | public OperationResult getResult() { 59 | return result; 60 | } 61 | 62 | public Object getBatchResults() { 63 | return result.results; 64 | } 65 | 66 | public void handleError(MethodChannel.Result result) { 67 | result.error(this.result.errorCode, this.result.errorMessage, this.result.data); 68 | } 69 | 70 | @Override 71 | public boolean getNoResult() { 72 | return noResult; 73 | } 74 | 75 | public void handleSuccess(List results) { 76 | if (!getNoResult()) { 77 | results.add(getBatchResults()); 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/ExecuteOperation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import com.tekartik.sqflite.SqlCommand; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import io.flutter.plugin.common.MethodChannel; 9 | 10 | /** 11 | * Created by alex on 09/01/18. 12 | */ 13 | 14 | public class ExecuteOperation extends BaseReadOperation { 15 | final Map map = new HashMap<>(); 16 | final SqlCommand command; 17 | final MethodChannel.Result result; 18 | 19 | public ExecuteOperation(MethodChannel.Result result, SqlCommand command) { 20 | this.result = result; 21 | this.command = command; 22 | } 23 | 24 | @Override 25 | protected OperationResult getResult() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public String getMethod() { 31 | return null; 32 | } 33 | 34 | @SuppressWarnings("unchecked") 35 | @Override 36 | public T getArgument(String key) { 37 | return (T) map.get(key); 38 | } 39 | 40 | @Override 41 | public void error(String errorCode, String errorMessage, Object data) { 42 | result.error(errorCode, errorMessage, data); 43 | } 44 | 45 | @Override 46 | public void success(Object results) { 47 | result.success(results); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/MethodCallOperation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import io.flutter.plugin.common.MethodCall; 4 | import io.flutter.plugin.common.MethodChannel; 5 | 6 | /** 7 | * Operation for Method call 8 | */ 9 | 10 | public class MethodCallOperation extends BaseOperation { 11 | final MethodCall methodCall; 12 | final Result result; 13 | 14 | class Result implements OperationResult { 15 | 16 | final MethodChannel.Result result; 17 | 18 | Result(MethodChannel.Result result) { 19 | this.result = result; 20 | } 21 | 22 | @Override 23 | public void success(Object results) { 24 | result.success(results); 25 | } 26 | 27 | @Override 28 | public void error(String errorCode, String errorMessage, Object data) { 29 | result.error(errorCode, errorMessage, data); 30 | } 31 | 32 | } 33 | 34 | public MethodCallOperation(MethodCall methodCall, MethodChannel.Result result) { 35 | this.methodCall = methodCall; 36 | this.result = new Result(result); 37 | } 38 | 39 | @Override 40 | public String getMethod() { 41 | return methodCall.method; 42 | } 43 | 44 | @Override 45 | public T getArgument(String key) { 46 | return methodCall.argument(key); 47 | } 48 | 49 | @Override 50 | public OperationResult getResult() { 51 | return result; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/Operation.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import com.tekartik.sqflite.SqlCommand; 4 | 5 | /** 6 | * Created by alex on 09/01/18. 7 | */ 8 | 9 | public interface Operation extends OperationResult { 10 | 11 | String getMethod(); 12 | 13 | T getArgument(String key); 14 | 15 | SqlCommand getSqlCommand(); 16 | 17 | boolean getNoResult(); 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/OperationResult.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | /** 4 | * Created by alex on 09/01/18. 5 | */ 6 | 7 | public interface OperationResult { 8 | void error(final String errorCode, final String errorMessage, final Object data); 9 | 10 | void success(final Object results); 11 | } 12 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/operation/SqlErrorInfo.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.operation; 2 | 3 | import com.tekartik.sqflite.SqlCommand; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import static com.tekartik.sqflite.Constant.PARAM_SQL; 9 | import static com.tekartik.sqflite.Constant.PARAM_SQL_ARGUMENTS; 10 | 11 | public class SqlErrorInfo { 12 | 13 | static public Map getMap(Operation operation) { 14 | Map map = null; 15 | SqlCommand command = operation.getSqlCommand(); 16 | if (command != null) { 17 | map = new HashMap<>(); 18 | map.put(PARAM_SQL, command.getSql()); 19 | map.put(PARAM_SQL_ARGUMENTS, command.getRawSqlArguments()); 20 | } 21 | return map; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/com/tekartik/sqflite/utils/SqlUtils.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite.utils; 2 | 3 | public class SqlUtils { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLCode.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public class SQLCode { 4 | /* sqlite result codes: */ 5 | public static final int OK = 0; 6 | public static final int ERROR = 1; 7 | public static final int INTERNAL = 2; 8 | public static final int PERM = 3; 9 | public static final int ABORT = 4; 10 | public static final int BUSY = 5; 11 | /* TBD TODO: ... */ 12 | public static final int CONSTRAINT = 19; 13 | public static final int MISMATCH = 20; 14 | public static final int MISUSE = 21; 15 | /* TBD TODO: ... */ 16 | public static final int ROW = 100; 17 | public static final int DONE = 101; 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLColumnType.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public class SQLColumnType { 4 | public static final int INTEGER = 1; // SQLITE_INTEGER 5 | public static final int REAL = 2; // SQLITE_FLOAT 6 | public static final int TEXT = 3; 7 | public static final int BLOB = 4; 8 | public static final int NULL = 5; 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLDatabaseHandle.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | /* package */ interface SQLDatabaseHandle { 4 | public int open(); 5 | public int close(); 6 | public int keyNativeString(String key); 7 | public boolean isOpen(); 8 | public SQLStatementHandle newStatementHandle(String sql); 9 | public long getLastInsertRowid(); 10 | public int getTotalChanges(); 11 | public String getLastErrorMessage(); 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLGDatabaseHandle.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | /* package */ class SQLGDatabaseHandle implements SQLDatabaseHandle { 4 | public SQLGDatabaseHandle(String filename, int flags) { 5 | dbfilename = filename; 6 | openflags = flags; 7 | } 8 | 9 | @Override 10 | public int open() { 11 | /* check state (should be checked by caller): */ 12 | if (dbfilename == null || dbhandle != 0) return SQLCode.MISUSE; 13 | 14 | long handle = SQLiteNative.sqlc_db_open(dbfilename, openflags); 15 | if (handle < 0) return (int)(-handle); 16 | 17 | dbhandle = handle; 18 | return SQLCode.OK; /* 0 */ 19 | } 20 | 21 | @Override 22 | public int keyNativeString(String key) { 23 | /* check state (should be checked by caller): */ 24 | if (dbhandle == 0) return SQLCode.MISUSE; 25 | 26 | return SQLiteNative.sqlc_db_key_native_string(this.dbhandle, key); 27 | } 28 | 29 | @Override 30 | public int close() { 31 | /* check state (should be checked by caller): */ 32 | if (dbhandle == 0) return SQLCode.MISUSE; 33 | 34 | return SQLiteNative.sqlc_db_close(this.dbhandle); 35 | } 36 | 37 | @Override 38 | public boolean isOpen() { 39 | return (dbhandle != 0); 40 | } 41 | 42 | @Override 43 | public SQLStatementHandle newStatementHandle(String sql) { 44 | /* check state (should be checked by caller): */ 45 | if (dbhandle == 0) return null; 46 | 47 | return new SQLGStatementHandle(sql); 48 | } 49 | 50 | @Override 51 | public long getLastInsertRowid() { 52 | /* check state (should be checked by caller): */ 53 | if (dbhandle == 0) return -1; /* illegit value */ 54 | 55 | return SQLiteNative.sqlc_db_last_insert_rowid(dbhandle); 56 | } 57 | 58 | @Override 59 | public int getTotalChanges() { 60 | /* check state (should be checked by caller): */ 61 | if (dbhandle == 0) return -1; /* illegit value */ 62 | 63 | return SQLiteNative.sqlc_db_total_changes(dbhandle); 64 | } 65 | 66 | @Override 67 | public String getLastErrorMessage() { 68 | /* check state (should be checked by caller): */ 69 | if (dbhandle == 0) return null; /* illegit value */ 70 | return SQLiteNative.sqlc_db_errmsg_native(dbhandle); 71 | } 72 | 73 | // XXX TODO make this reusable: 74 | private class SQLGStatementHandle implements SQLStatementHandle { 75 | private SQLGStatementHandle(String sql) { 76 | this.sql = sql; 77 | } 78 | 79 | @Override 80 | public int prepare() { 81 | /* check state (should be checked by caller): */ 82 | if (sql == null || sthandle != 0) return SQLCode.MISUSE; 83 | 84 | long sh = SQLiteNative.sqlc_db_prepare_st(dbhandle, sql); 85 | if (sh < 0) return (int)(-sh); 86 | 87 | sthandle = sh; 88 | return SQLCode.OK; /* 0 */ 89 | } 90 | 91 | @Override 92 | public int bindDouble(int pos, double val) { 93 | /* check state (should be checked by caller): */ 94 | if (sthandle == 0) return SQLCode.MISUSE; 95 | 96 | return SQLiteNative.sqlc_st_bind_double(this.sthandle, pos, val); 97 | } 98 | 99 | @Override 100 | public int bindInteger(int pos, int val) { 101 | /* check state (should be checked by caller): */ 102 | if (sthandle == 0) return SQLCode.MISUSE; 103 | 104 | return SQLiteNative.sqlc_st_bind_int(this.sthandle, pos, val); 105 | } 106 | 107 | @Override 108 | public int bindLong(int pos, long val) { 109 | /* check state (should be checked by caller): */ 110 | if (sthandle == 0) return SQLCode.MISUSE; 111 | 112 | return SQLiteNative.sqlc_st_bind_long(this.sthandle, pos, val); 113 | } 114 | 115 | @Override 116 | public int bindNull(int pos) { 117 | /* check state (should be checked by caller): */ 118 | if (sthandle == 0) return SQLCode.MISUSE; 119 | 120 | return SQLiteNative.sqlc_st_bind_null(this.sthandle, pos); 121 | } 122 | 123 | @Override 124 | public int bindTextNativeString(int pos, String val) { 125 | /* check state (should be checked by caller): */ 126 | if (sthandle == 0) return SQLCode.MISUSE; 127 | 128 | return SQLiteNative.sqlc_st_bind_text_native(this.sthandle, pos, val); 129 | } 130 | 131 | @Override 132 | public int step() { 133 | /* check state (should be checked by caller): */ 134 | if (sthandle == 0) return SQLCode.MISUSE; 135 | 136 | return SQLiteNative.sqlc_st_step(this.sthandle); 137 | } 138 | 139 | @Override 140 | public int getColumnCount() { 141 | /* check state (should be checked by caller): */ 142 | if (sthandle == 0) return -1; 143 | 144 | return SQLiteNative.sqlc_st_column_count(this.sthandle); 145 | } 146 | 147 | @Override 148 | public String getColumnName(int col) { 149 | /* check state (should be checked by caller): */ 150 | if (sthandle == 0) return null; 151 | 152 | return SQLiteNative.sqlc_st_column_name(this.sthandle, col); 153 | } 154 | 155 | @Override 156 | public int getColumnType(int col) { 157 | /* check state (should be checked by caller): */ 158 | if (sthandle == 0) return -1; 159 | 160 | return SQLiteNative.sqlc_st_column_type(this.sthandle, col); 161 | } 162 | 163 | @Override 164 | public double getColumnDouble(int col) { 165 | /* check state (should be checked by caller): */ 166 | if (sthandle == 0) return -1; 167 | 168 | return SQLiteNative.sqlc_st_column_double(this.sthandle, col); 169 | } 170 | 171 | @Override 172 | public int getColumnInteger(int col) { 173 | /* check state (should be checked by caller): */ 174 | if (sthandle == 0) return -1; 175 | 176 | return SQLiteNative.sqlc_st_column_int(this.sthandle, col); 177 | } 178 | 179 | @Override 180 | public long getColumnLong(int col) { 181 | /* check state (should be checked by caller): */ 182 | if (sthandle == 0) return -1; 183 | 184 | return SQLiteNative.sqlc_st_column_long(this.sthandle, col); 185 | } 186 | 187 | @Override 188 | public String getColumnTextNativeString(int col) { 189 | /* check state (should be checked by caller): */ 190 | if (sthandle == 0) return null; 191 | 192 | return SQLiteNative.sqlc_st_column_text_native(this.sthandle, col); 193 | } 194 | 195 | @Override 196 | public int finish() { 197 | /* check state (should be checked by caller): */ 198 | if (sthandle == 0) return SQLCode.MISUSE; 199 | 200 | long mysthandle = sthandle; 201 | sql = null; 202 | sthandle = 0; 203 | 204 | return SQLiteNative.sqlc_st_finish(mysthandle); 205 | } 206 | 207 | String sql = null; 208 | private long sthandle = 0; 209 | } 210 | 211 | String dbfilename = null; 212 | int openflags = 0; 213 | private long dbhandle = 0; 214 | } 215 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLStatementHandle.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | /* package */ interface SQLStatementHandle { 4 | public int prepare(); 5 | public int bindDouble(int pos, double val); 6 | public int bindInteger(int pos, int val); 7 | public int bindLong(int pos, long val); 8 | public int bindNull(int pos); 9 | public int bindTextNativeString(int col, String val); 10 | public int step(); 11 | public int getColumnCount(); 12 | public String getColumnName(int col); 13 | public double getColumnDouble(int col); 14 | public int getColumnInteger(int col); 15 | public long getColumnLong(int col); 16 | public String getColumnTextNativeString(int col); 17 | public int getColumnType(int col); 18 | public int finish(); 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteConnection.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public interface SQLiteConnection { 4 | public void dispose() throws java.sql.SQLException; 5 | public void keyNativeString(String key) throws java.sql.SQLException; 6 | public SQLiteStatement prepareStatement(String sql) throws java.sql.SQLException; 7 | public long getLastInsertRowid() throws java.sql.SQLException; 8 | public int getTotalChanges() throws java.sql.SQLException; 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public interface SQLiteConnectionFactory { 4 | public SQLiteConnection newSQLiteConnection(String filename, int flags) throws java.sql.SQLException; 5 | } 6 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteConnector.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public class SQLiteConnector implements SQLiteConnectionFactory { 4 | static boolean isLibLoaded = false; 5 | 6 | public SQLiteConnector() { 7 | if (!isLibLoaded) { 8 | System.loadLibrary("sqlc-native-driver"); 9 | 10 | if (SQLiteNative.sqlc_api_version_check(SQLiteNative.SQLC_API_VERSION) != SQLCode.OK) { 11 | throw new RuntimeException("native library version mismatch"); 12 | } 13 | 14 | isLibLoaded = true; 15 | } 16 | } 17 | 18 | @Override 19 | public SQLiteConnection newSQLiteConnection(String filename, int flags) throws java.sql.SQLException { 20 | return new SQLiteGlueConnection(filename, flags); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteGlueConnection.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | /* package */ class SQLiteGlueConnection implements SQLiteConnection { 4 | public SQLiteGlueConnection(String filename, int flags) throws java.sql.SQLException 5 | { 6 | /* check param(s): */ 7 | if (filename == null) throw new java.sql.SQLException("null argument", "failed", SQLCode.MISUSE); 8 | 9 | SQLDatabaseHandle mydb = new SQLGDatabaseHandle(filename, flags); 10 | int rc = mydb.open(); 11 | 12 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_open_v2 failure: " + db.getLastErrorMessage(), "failure", rc); 13 | this.db = mydb; 14 | } 15 | 16 | @Override 17 | public void dispose() throws java.sql.SQLException { 18 | /* check state: */ 19 | if (db == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 20 | 21 | int rc = db.close(); 22 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_close failure: " + db.getLastErrorMessage(), "failure", rc); 23 | db = null; 24 | } 25 | 26 | @Override 27 | public void keyNativeString(String key) throws java.sql.SQLException { 28 | /* check state: */ 29 | if (db == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 30 | 31 | int rc = db.keyNativeString(key); 32 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_key failure: " + db.getLastErrorMessage(), "failure", rc); 33 | } 34 | 35 | @Override 36 | public SQLiteStatement prepareStatement(String sql) throws java.sql.SQLException { 37 | /* check state: */ 38 | if (db == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 39 | 40 | /* check param(s): */ 41 | if (sql == null) throw new java.sql.SQLException("null argument", "failed", SQLCode.MISUSE); 42 | 43 | SQLGStatement st = new SQLGStatement(sql); 44 | int rc = st.prepare(); 45 | if (rc != SQLCode.OK) { 46 | throw new java.sql.SQLException("sqlite3_prepare_v2 failure: " + db.getLastErrorMessage(), "failure", rc); 47 | } 48 | 49 | return st; 50 | } 51 | 52 | @Override 53 | public long getLastInsertRowid() throws java.sql.SQLException { 54 | /* check state: */ 55 | if (db == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 56 | 57 | return db.getLastInsertRowid(); 58 | } 59 | 60 | @Override 61 | public int getTotalChanges() throws java.sql.SQLException { 62 | /* check state: */ 63 | if (db == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 64 | 65 | return db.getTotalChanges(); 66 | } 67 | 68 | // XXX TODO make this reusable: 69 | private class SQLGStatement implements SQLiteStatement { 70 | SQLGStatement(String sql) { 71 | this.sql = sql; 72 | this.sthandle = db.newStatementHandle(sql); 73 | } 74 | 75 | int prepare() { 76 | // TBD check state here? 77 | return sthandle.prepare(); 78 | } 79 | 80 | @Override 81 | public void bindDouble(int pos, double val) throws java.sql.SQLException { 82 | /* check state: */ 83 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 84 | 85 | int rc = sthandle.bindDouble(pos, val); 86 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_bind_double failure: " + db.getLastErrorMessage(), "failure", rc); 87 | } 88 | 89 | @Override 90 | public void bindInteger(int pos, int val) throws java.sql.SQLException { 91 | /* check state: */ 92 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 93 | 94 | int rc = sthandle.bindInteger(pos, val); 95 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_bind_int failure: " + db.getLastErrorMessage(), "failure", rc); 96 | } 97 | 98 | @Override 99 | public void bindLong(int pos, long val) throws java.sql.SQLException { 100 | /* check state: */ 101 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 102 | 103 | int rc = sthandle.bindLong(pos, val); 104 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_bind_int64 (long) failure: " + db.getLastErrorMessage(), "failure", rc); 105 | } 106 | 107 | @Override 108 | public void bindNull(int pos) throws java.sql.SQLException { 109 | /* check state: */ 110 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 111 | 112 | int rc = sthandle.bindNull(pos); 113 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_bind_null failure: " + db.getLastErrorMessage(), "failure", rc); 114 | } 115 | 116 | @Override 117 | public void bindTextNativeString(int pos, String val) throws java.sql.SQLException { 118 | /* check state: */ 119 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 120 | 121 | /* check param(s): */ 122 | if (val == null) throw new java.sql.SQLException("null argument", "failed", SQLCode.MISUSE); 123 | 124 | int rc = sthandle.bindTextNativeString(pos, val); 125 | if (rc != SQLCode.OK) throw new java.sql.SQLException("sqlite3_bind_text failure: " + db.getLastErrorMessage(), "failure", rc); 126 | } 127 | 128 | @Override 129 | public boolean step() throws java.sql.SQLException { 130 | /* check state: */ 131 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 132 | 133 | int rc = sthandle.step(); 134 | if (rc != SQLCode.OK && rc != SQLCode.ROW && rc != SQLCode.DONE) { 135 | throw new java.sql.SQLException("sqlite3_step failure: " + db.getLastErrorMessage(), "failure", rc); 136 | } 137 | 138 | hasRow = (rc == SQLCode.ROW); 139 | if (hasRow) { 140 | columnCount = sthandle.getColumnCount(); 141 | } else columnCount = 0; 142 | 143 | return hasRow; 144 | } 145 | 146 | @Override 147 | public int getColumnCount() throws java.sql.SQLException { 148 | /* check state: */ 149 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 150 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 151 | 152 | return columnCount; 153 | } 154 | 155 | @Override 156 | public String getColumnName(int col) throws java.sql.SQLException { 157 | /* check state: */ 158 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 159 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 160 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 161 | 162 | return sthandle.getColumnName(col); 163 | } 164 | 165 | @Override 166 | public int getColumnType(int col) throws java.sql.SQLException { 167 | /* check state: */ 168 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 169 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 170 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 171 | 172 | return sthandle.getColumnType(col); 173 | } 174 | 175 | @Override 176 | public double getColumnDouble(int col) throws java.sql.SQLException { 177 | /* check state: */ 178 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 179 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 180 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 181 | 182 | return sthandle.getColumnDouble(col); 183 | } 184 | 185 | @Override 186 | public int getColumnInteger(int col) throws java.sql.SQLException { 187 | /* check state: */ 188 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 189 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 190 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 191 | 192 | return sthandle.getColumnInteger(col); 193 | } 194 | 195 | @Override 196 | public long getColumnLong(int col) throws java.sql.SQLException { 197 | /* check state: */ 198 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 199 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 200 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 201 | 202 | return sthandle.getColumnLong(col); 203 | } 204 | 205 | @Override 206 | public String getColumnTextNativeString(int col) throws java.sql.SQLException { 207 | /* check state: */ 208 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 209 | if (!hasRow) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 210 | if (col < 0 || col >= columnCount) throw new java.sql.SQLException("no result available", "failed", SQLCode.MISUSE); 211 | 212 | return sthandle.getColumnTextNativeString(col); 213 | } 214 | 215 | @Override 216 | public void dispose() throws java.sql.SQLException { 217 | /* check state: */ 218 | if (sthandle == null) throw new java.sql.SQLException("already disposed", "failed", SQLCode.MISUSE); 219 | 220 | /* NOTE: no need to check the return code in this case. */ 221 | sthandle.finish(); 222 | sthandle = null; 223 | } 224 | 225 | private SQLStatementHandle sthandle = null; 226 | private String sql = null; 227 | private boolean hasRow = false; 228 | private int columnCount = 0; 229 | } 230 | 231 | private SQLDatabaseHandle db = null; 232 | } 233 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteNative.java: -------------------------------------------------------------------------------- 1 | /* !---- DO NOT EDIT: This file autogenerated by com/jogamp/gluegen/JavaEmitter.java on Wed Oct 28 13:07:37 CET 2015 ----! */ 2 | 3 | package io.liteglue; 4 | 5 | //import com.jogamp.gluegen.runtime.*; 6 | //import com.jogamp.common.os.*; 7 | //import com.jogamp.common.nio.*; 8 | //import java.nio.*; 9 | 10 | public class SQLiteNative { 11 | 12 | public static final int SQLC_API_VERSION = 1; 13 | public static final int SQLC_OPEN_READONLY = 0x00001; 14 | public static final int SQLC_OPEN_READWRITE = 0x00002; 15 | public static final int SQLC_OPEN_CREATE = 0x00004; 16 | public static final int SQLC_OPEN_URI = 0x00040; 17 | public static final int SQLC_OPEN_MEMORY = 0x00080; 18 | public static final int SQLC_OPEN_NOMUTEX = 0x08000; 19 | public static final int SQLC_OPEN_FULLMUTEX = 0x10000; 20 | public static final int SQLC_OPEN_SHAREDCACHE = 0x20000; 21 | public static final int SQLC_OPEN_PRIVATECACHE = 0x40000; 22 | public static final int SQLC_RESULT_OK = 0; 23 | public static final int SQLC_RESULT_ERROR = 1; 24 | public static final int SQLC_RESULT_INTERNAL = 2; 25 | public static final int SQLC_RESULT_PERM = 3; 26 | public static final int SQLC_RESULT_ABORT = 4; 27 | public static final int SQLC_RESULT_CONSTRAINT = 19; 28 | public static final int SQLC_RESULT_MISMATCH = 20; 29 | public static final int SQLC_RESULT_MISUSE = 21; 30 | public static final int SQLC_RESULT_ROW = 100; 31 | public static final int SQLC_RESULT_DONE = 101; 32 | public static final int SQLC_INTEGER = 1; 33 | public static final int SQLC_FLOAT = 2; 34 | public static final int SQLC_TEXT = 3; 35 | public static final int SQLC_BLOB = 4; 36 | public static final int SQLC_NULL = 5; 37 | 38 | /** Interface to C language function:
sqlc_handle_t sqlc_api_db_open(int sqlc_api_version, const char * filename, int flags); */ 39 | public static native long sqlc_api_db_open(int sqlc_api_version, String filename, int flags); 40 | 41 | /** Interface to C language function:
int sqlc_api_version_check(int sqlc_api_version); */ 42 | public static native int sqlc_api_version_check(int sqlc_api_version); 43 | 44 | /** Interface to C language function:
int sqlc_db_close(sqlc_handle_t db); */ 45 | public static native int sqlc_db_close(long db); 46 | 47 | /** Interface to C language function:
int sqlc_db_errcode(sqlc_handle_t db); */ 48 | public static native int sqlc_db_errcode(long db); 49 | 50 | /** Interface to C language function:
const char * sqlc_db_errmsg_native(sqlc_handle_t db); */ 51 | public static native String sqlc_db_errmsg_native(long db); 52 | 53 | /** Interface to C language function:
int sqlc_db_key_native_string(sqlc_handle_t db, char * key_string); */ 54 | public static native int sqlc_db_key_native_string(long db, String key_string); 55 | 56 | /** Interface to C language function:
sqlc_long_t sqlc_db_last_insert_rowid(sqlc_handle_t db); */ 57 | public static native long sqlc_db_last_insert_rowid(long db); 58 | 59 | /** Interface to C language function:
sqlc_handle_t sqlc_db_open(const char * filename, int flags); */ 60 | public static native long sqlc_db_open(String filename, int flags); 61 | 62 | /** Interface to C language function:
sqlc_handle_t sqlc_db_prepare_st(sqlc_handle_t db, const char * sql); */ 63 | public static native long sqlc_db_prepare_st(long db, String sql); 64 | 65 | /** Interface to C language function:
int sqlc_db_total_changes(sqlc_handle_t db); */ 66 | public static native int sqlc_db_total_changes(long db); 67 | 68 | /** Interface to C language function:
const char * sqlc_errstr_native(int errcode); */ 69 | public static native String sqlc_errstr_native(int errcode); 70 | 71 | /** Interface to C language function:
int sqlc_st_bind_double(sqlc_handle_t st, int pos, double val); */ 72 | public static native int sqlc_st_bind_double(long st, int pos, double val); 73 | 74 | /** Interface to C language function:
int sqlc_st_bind_int(sqlc_handle_t st, int pos, int val); */ 75 | public static native int sqlc_st_bind_int(long st, int pos, int val); 76 | 77 | /** Interface to C language function:
int sqlc_st_bind_long(sqlc_handle_t st, int pos, sqlc_long_t val); */ 78 | public static native int sqlc_st_bind_long(long st, int pos, long val); 79 | 80 | /** Interface to C language function:
int sqlc_st_bind_null(sqlc_handle_t st, int pos); */ 81 | public static native int sqlc_st_bind_null(long st, int pos); 82 | 83 | /** Interface to C language function:
int sqlc_st_bind_text_native(sqlc_handle_t st, int col, const char * val); */ 84 | public static native int sqlc_st_bind_text_native(long st, int col, String val); 85 | 86 | /** Interface to C language function:
int sqlc_st_column_count(sqlc_handle_t st); */ 87 | public static native int sqlc_st_column_count(long st); 88 | 89 | /** Interface to C language function:
double sqlc_st_column_double(sqlc_handle_t st, int col); */ 90 | public static native double sqlc_st_column_double(long st, int col); 91 | 92 | /** Interface to C language function:
int sqlc_st_column_int(sqlc_handle_t st, int col); */ 93 | public static native int sqlc_st_column_int(long st, int col); 94 | 95 | /** Interface to C language function:
sqlc_long_t sqlc_st_column_long(sqlc_handle_t st, int col); */ 96 | public static native long sqlc_st_column_long(long st, int col); 97 | 98 | /** Interface to C language function:
const char * sqlc_st_column_name(sqlc_handle_t st, int col); */ 99 | public static native String sqlc_st_column_name(long st, int col); 100 | 101 | /** Interface to C language function:
const char * sqlc_st_column_text_native(sqlc_handle_t st, int col); */ 102 | public static native String sqlc_st_column_text_native(long st, int col); 103 | 104 | /** Interface to C language function:
int sqlc_st_column_type(sqlc_handle_t st, int col); */ 105 | public static native int sqlc_st_column_type(long st, int col); 106 | 107 | /** Interface to C language function:
int sqlc_st_finish(sqlc_handle_t st); */ 108 | public static native int sqlc_st_finish(long st); 109 | 110 | /** Interface to C language function:
int sqlc_st_step(sqlc_handle_t st); */ 111 | public static native int sqlc_st_step(long st); 112 | 113 | 114 | } // end of class SQLiteNative 115 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteOpenFlags.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public class SQLiteOpenFlags { 4 | public static final int READONLY = 0x0001; 5 | public static final int READWRITE = 0x0002; 6 | public static final int CREATE = 0x0004; 7 | /* TBD TODO: ... */ 8 | } 9 | -------------------------------------------------------------------------------- /android/src/main/java/io/liteglue/SQLiteStatement.java: -------------------------------------------------------------------------------- 1 | package io.liteglue; 2 | 3 | public interface SQLiteStatement { 4 | public void bindDouble(int pos, double val) throws java.sql.SQLException; 5 | public void bindInteger(int pos, int val) throws java.sql.SQLException; 6 | public void bindLong(int pos, long val) throws java.sql.SQLException; 7 | public void bindNull(int pos) throws java.sql.SQLException; 8 | public void bindTextNativeString(int pos, String val) throws java.sql.SQLException; 9 | public boolean step() throws java.sql.SQLException; 10 | public int getColumnCount() throws java.sql.SQLException; 11 | public String getColumnName(int col) throws java.sql.SQLException; 12 | public double getColumnDouble(int col) throws java.sql.SQLException; 13 | public int getColumnInteger(int col) throws java.sql.SQLException; 14 | public long getColumnLong(int col) throws java.sql.SQLException; 15 | public String getColumnTextNativeString(int col) throws java.sql.SQLException; 16 | public int getColumnType(int col) throws java.sql.SQLException; 17 | public void dispose() throws java.sql.SQLException; 18 | } 19 | -------------------------------------------------------------------------------- /android/src/test/java/com/tekartik/sqflite/SqlCommandTest.java: -------------------------------------------------------------------------------- 1 | package com.tekartik.sqflite; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.junit.Assert.assertArrayEquals; 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertNotEquals; 12 | 13 | /** 14 | * Constants between dart & Java world 15 | */ 16 | 17 | public class SqlCommandTest { 18 | 19 | 20 | @Test 21 | public void noParam() { 22 | SqlCommand command = new SqlCommand(null, null); 23 | assertEquals(command.getSql(), null); 24 | assertEquals(command.getRawSqlArguments(), new ArrayList<>()); 25 | } 26 | 27 | @Test 28 | public void sqlArguments() { 29 | List arguments = Arrays.asList((Object) 1L, 2, "text", 30 | 1.234f, 31 | 4.5678, // double 32 | new byte[]{1, 2, 3}); 33 | SqlCommand command = new SqlCommand(null, arguments); 34 | /* 35 | assertEquals(Arrays.asList(1L, 2, "text", 36 | 1.234f, 37 | 4.5678, // double 38 | new byte[] {1,2, 3}), command.getRawSqlArguments()); 39 | */ 40 | assertArrayEquals(new Object[]{1L, 2, "text", 41 | 1.234f, 42 | 4.5678, // double 43 | new byte[]{1, 2, 3}}, command.getSqlArguments()); 44 | assertArrayEquals(new String[]{"1", "2", "text", 45 | "1.234", 46 | "4.5678", // double 47 | "[1, 2, 3]"}, command.getQuerySqlArguments()); 48 | 49 | } 50 | 51 | @Test 52 | public void equals() { 53 | SqlCommand command1 = new SqlCommand(null, null); 54 | SqlCommand command2 = new SqlCommand(null, new ArrayList()); 55 | assertEquals(command1, command2); 56 | command1 = new SqlCommand("", null); 57 | assertNotEquals(command1, command2); 58 | assertNotEquals(command2, command1); 59 | command1 = new SqlCommand(null, Arrays.asList((Object) "test")); 60 | assertNotEquals(command1, command2); 61 | assertNotEquals(command2, command1); 62 | command2 = new SqlCommand(null, Arrays.asList((Object) "test")); 63 | assertEquals(command1, command2); 64 | command1 = new SqlCommand(null, Arrays.asList((Object) "test_")); 65 | assertNotEquals(command1, command2); 66 | assertNotEquals(command2, command1); 67 | command1 = new SqlCommand(null, Arrays.asList((Object) new byte[]{1, 2, 3})); 68 | command2 = new SqlCommand(null, Arrays.asList((Object) new byte[]{1, 2, 3})); 69 | assertEquals(command1, command2); 70 | command1 = new SqlCommand(null, Arrays.asList((Object) new byte[]{1, 2})); 71 | assertNotEquals(command1, command2); 72 | assertNotEquals(command2, command1); 73 | } 74 | 75 | @Test 76 | public void sanitizeQuery() { 77 | SqlCommand command = new SqlCommand("?", Arrays.asList((Object) 1)); 78 | assertEquals(new SqlCommand("1", null), command.sanitizeForQuery()); 79 | command = new SqlCommand("?", Arrays.asList((Object) 1, 2)); 80 | assertEquals(command, command.sanitizeForQuery()); 81 | command = new SqlCommand("?", null); 82 | assertEquals(command, command.sanitizeForQuery()); 83 | command = new SqlCommand(null, null); 84 | assertEquals(command, command.sanitizeForQuery()); 85 | command = new SqlCommand("?,?,?,?,?,?", Arrays.asList((Object) 1L, 2, "text", 86 | 1.234f, 87 | 4.5678, // double 88 | new byte[]{1, 2, 3})); 89 | assertEquals(new SqlCommand("1,2,?,?,?,?", Arrays.asList((Object) "text", 90 | 1.234f, 91 | 4.5678, // double 92 | new byte[]{1, 2, 3})), command.sanitizeForQuery()); 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /doc/dev_tips.md: -------------------------------------------------------------------------------- 1 | # Dev tips 2 | 3 | ## Extract SQLite database on Android 4 | 5 | In Android Studio (> 3.0.1) 6 | * Open `Device File Explorer via View > Tool Windows > Device File Explorer` 7 | * Go to `data/data//databases`, where `` is the name of your package. 8 | Location might depends how the path was specified (assuming here that are using `getDatabasesPath` to get its base location) 9 | * Right click on the database and select Save As.... Save it anywhere you want on your PC. -------------------------------------------------------------------------------- /doc/how_to.md: -------------------------------------------------------------------------------- 1 | # Sqflite guide 2 | 3 | * How to [Open a database](opening_db.md) 4 | * How to [Open an asset database](opening_asset_db.md) 5 | * Solve you [build and runtime issues](troubleshooting.md) 6 | * Some personal [usage recommendations](usage_recommendations.md) 7 | * Some [dev tips](dev_tips.md) 8 | 9 | ## Development guide 10 | 11 | ### Check list 12 | 13 | * run test 14 | * no warning 15 | * string mode / implicit-casts: false 16 | 17 | ```` 18 | # quick run before commiting 19 | 20 | dartfmt -w lib test example 21 | flutter analyze lib test 22 | flutter test 23 | 24 | flutter run 25 | flutter run --preview-dart-2 26 | 27 | # Using preview dart 2 28 | flutter test --preview-dart-2 29 | ```` 30 | 31 | ### Publishing 32 | 33 | flutter packages pub publish 34 | -------------------------------------------------------------------------------- /doc/opening_asset_db.md: -------------------------------------------------------------------------------- 1 | # Open an asset database 2 | 3 | * Add the asset in your file system at the root of your project. Typically 4 | I would create an `assets` folder and put my file in it: 5 | ```` 6 | assets/examples.db 7 | ```` 8 | 9 | * Specify the asset(s) in your `pubspec.yaml` in the flutter section 10 | ```` 11 | flutter: 12 | assets: 13 | - assets/example.db 14 | ```` 15 | 16 | * Copy the database to your file system 17 | ```` 18 | Directory documentsDirectory = await getApplicationDocumentsDirectory(); 19 | String path = join(documentsDirectory.path, "asset_example.db"); 20 | 21 | // delete existing if any 22 | await deleteDatabase(path); 23 | 24 | // Copy from asset 25 | ByteData data = await rootBundle.load(join("assets", "example.db")); 26 | List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); 27 | await new File(path).writeAsBytes(bytes); 28 | ```` 29 | 30 | * Open it! 31 | ```` 32 | // open the database 33 | Database db = await openDatabase(path); 34 | ```` -------------------------------------------------------------------------------- /doc/opening_db.md: -------------------------------------------------------------------------------- 1 | # Opening a database 2 | 3 | ## finding a location path for the database 4 | 5 | Sqflite provides a basic location strategy using the databases path on Android and the Documents folder on iOS, as 6 | recommended on both platform. The location can be retrieved using `getDatabasesPath`. 7 | 8 | ```dart 9 | var databasesPath = await getDatabasesPath(); 10 | var path = join(databasesPath, dbName); 11 | 12 | // Make sure the directory exists 13 | try { 14 | await documentsDirectory.create(recursive: true); 15 | } catch (_) {} 16 | ``` 17 | 18 | ## Read-write 19 | 20 | Opening a database in read-write mode is the default. One can specify a version to perform 21 | migration strategy, can configure the database and its version. 22 | 23 | ### Configuration 24 | 25 | 26 | `onConfigure` is the first optional callback called. It allows to perform database initialization 27 | such as supporting cascade delete 28 | 29 | ```dart 30 | _onConfigure(Database db) async { 31 | // Add support for cascade delete 32 | await db.execute("PRAGMA foreign_keys = ON"); 33 | } 34 | 35 | var db = await openDatabase(path, onConfigure: _onConfigure); 36 | 37 | ``` 38 | 39 | ### Preloading data 40 | 41 | You might want to preload you database when opened the first time. You can either 42 | * [Import an existing SQLite file](opening_asset_db.md) checking first whether the database file exists or not 43 | * Populate data during `onCreate`: 44 | 45 | 46 | ```dart 47 | _onCreate(Database db, int version) async { 48 | // Database is created, create the table 49 | await db.execute( 50 | "CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)"); 51 | } 52 | // populate data 53 | await db.insert(...); 54 | } 55 | 56 | // Open the database, specifying a version and an onCreate callback 57 | var db = await openDatabase(path, 58 | version: 1, 59 | onCreate: _onCreate); 60 | ``` 61 | ### Migration 62 | 63 | `onCreate`, `onUpdate`, `onDowngrade` is called if a `version` is specified. If the database does 64 | not exist, `onCreate` is called. If the new version requested is higher, `onUpdate` is called, otherwise 65 | (try to avoid this by always incrementing the database version), `onDowngrade` is called. For this 66 | later case, a special `onDowngradeDatabaseDelete` callback exist that will simply delete the database 67 | and call `onCreate` to create it. 68 | 69 | These 3 callbacks are called within a transaction just before the version is set on the database. 70 | 71 | 72 | ```dart 73 | _onCreate(Database db, int version) async { 74 | // Database is created, create the table 75 | await db.execute( 76 | "CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)"); 77 | } 78 | 79 | _onUpgrade(Database db, int oldVersion, int newVersion) async { 80 | // Database version is updated, alter the table 81 | await db.execute("ALTER TABLE Test ADD name TEXT"); 82 | } 83 | 84 | // Special callback used for onDowngrade here to recreate the database 85 | var db = await openDatabase(path, 86 | version: 1, 87 | onCreate: _onCreate, 88 | onUpgrade: _onUpgrade, 89 | onDowngrade: onDatabaseDowngradeDelete); 90 | ``` 91 | 92 | ### Post open callback 93 | 94 | For convenience, `onOpen` is called after the database version is set and before `openDatabase` returns. 95 | 96 | ```dart 97 | _onOpen(Database db) async { 98 | // Database is open, print its version 99 | print('db version ${await db.getVersion()}'); 100 | } 101 | 102 | var db = await openDatabase( 103 | path, 104 | onOpen: _onOpen, 105 | ); 106 | ``` 107 | ## Read-only 108 | 109 | ```dart 110 | // open the database in read-only mode 111 | var db = await openReadOnlyDatabase(path); 112 | ``` 113 | 114 | ## Prevent database locked issue 115 | 116 | It is strongly suggested to open a database only once. 117 | If you open the same database multiple times, you might encounter (at least on Android): 118 | 119 | android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 120 | 121 | Let's consider the following helper class 122 | 123 | ```dart 124 | class Helper { 125 | final String path; 126 | Helper(this.path); 127 | Database _db; 128 | 129 | Future getDb() async { 130 | if (_db == null) { 131 | _db = await openDatabase(path); 132 | } 133 | return _db; 134 | } 135 | } 136 | ``` 137 | 138 | Since `openDatabase` is async, there is a race condition risk where openDatabase 139 | might be called twice. You could fix this with the following: 140 | 141 | ```dart 142 | import 'package:synchronized/synchronized.dart'; 143 | 144 | class Helper { 145 | final String path; 146 | Helper(this.path); 147 | Database _db; 148 | final _lock = new Lock(); 149 | 150 | Future getDb() async { 151 | if (_db == null) { 152 | await _lock.synchronized(() async { 153 | // Check again once entering the synchronized block 154 | if (_db == null) { 155 | _db = await openDatabase(path); 156 | } 157 | }); 158 | } 159 | return _db; 160 | } 161 | } 162 | ``` -------------------------------------------------------------------------------- /doc/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | If you ran into build/runtime issues, please try the following: 4 | 5 | * Update flutter to the latest version (`flutter upgrade`) 6 | * Update sqflite dependencies to the latest version in your `pubspec.yaml` 7 | (`sqflite >= X.Y.Z`) 8 | * Update dependencies (`flutter packages upgrade`) 9 | * try `flutter clean` 10 | * Try deleting the flutter cache folder (which will 11 | downloaded again automatically) from your flutter installation folder `$FLUTTER_TOP/bin/cache` 12 | 13 | # Recommendations 14 | 15 | * Enable strong-mode 16 | * Disable implicit casts 17 | 18 | Sample `analysis_options.yaml` file: 19 | 20 | ``` 21 | analyzer: 22 | strong-mode: 23 | implicit-casts: false 24 | ``` 25 | 26 | # Common issues 27 | 28 | ## Cast error 29 | 30 | ``` 31 | Unhandled exception: type '_InternalLinkedHashMap' is not a subtype of type 'Map' 32 | where 33 | _InternalLinkedHashMap is from dart:collection 34 | Map is from dart:core 35 | String is from dart:core 36 | ``` 37 | 38 | Make sure you create object of type `Map` and not simply `Map` for records you 39 | insert and update. The option `implicit-casts: false` explained above helps to find such issues 40 | 41 | ## Debugging SQL commands 42 | 43 | A quick way to view SQL commands printed out is to call before opening any database 44 | 45 | ```dart 46 | await Sqflite.devSetDebugModeOn(true); 47 | ``` 48 | 49 | This call is on purpose deprecated to force removing it once the SQL issues has been resolved. -------------------------------------------------------------------------------- /doc/usage_recommendations.md: -------------------------------------------------------------------------------- 1 | Below are (or will be) personal recommendations on usage 2 | 3 | ## Single database connection 4 | 5 | The API is largely inspired from Android ContentProvider where a typical SQLite implementation means 6 | opening the database once on the first request and keep it open 7 | 8 | Personally I have one global reference Database in my flutter application to avoid lock issues. Opening the 9 | database should be safe if called multiple time. 10 | 11 | Keeping a reference only in a widget can cause issues with hot reload if the reference is lost (and the database not 12 | closed yet). 13 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .packages 5 | .pub/ 6 | build/ 7 | ios/.generated/ 8 | packages 9 | pubspec.lock 10 | .flutter-plugins 11 | sqflite_example.iml 12 | 13 | # local files 14 | .local/ 15 | tmp/ -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # sqflite_example 2 | 3 | Demonstrates how to use the [sqflite plugin](https://github.com/tekartik/sqflite). 4 | 5 | ## Quick test 6 | 7 | 8 | flutter run 9 | 10 | Specific app entry point 11 | 12 | flutter run -t lib/main.dart 13 | 14 | ## Getting Started 15 | 16 | For help getting started with Flutter, view the online 17 | [documentation](https://flutter.io/). 18 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false -------------------------------------------------------------------------------- /example/android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .gradle 4 | /local.properties 5 | .DS_Store 6 | /build 7 | /captures 8 | GeneratedPluginRegistrant.java 9 | 10 | /gradle/wrapper/gradle-wrapper.jar 11 | /gradlew 12 | /gradlew.bat 13 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withInputStream { stream -> 5 | localProperties.load(stream) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 27 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 26 | 27 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 28 | applicationId "com.terkartik.sqflite_example" 29 | } 30 | 31 | buildTypes { 32 | release { 33 | // Workaround for build bug 34 | profile { 35 | matchingFallbacks = ['debug', 'release'] 36 | } 37 | 38 | // TODO: Add your own signing config for the release build. 39 | // Signing with the debug keys for now, so `flutter run --release` works. 40 | signingConfig signingConfigs.debug 41 | } 42 | } 43 | } 44 | 45 | flutter { 46 | source '../..' 47 | } 48 | 49 | dependencies { 50 | testImplementation 'junit:junit:4.12' 51 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 52 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 53 | } 54 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yourcompany/sqflite_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yourcompany.sqflite_example; 2 | 3 | import android.os.Bundle; 4 | 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | GeneratedPluginRegistrant.registerWith(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.3' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 12 12:46:04 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withInputStream { stream -> plugins.load(stream) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/assets/example.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/assets/example.db -------------------------------------------------------------------------------- /example/assets/issue_64.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/assets/issue_64.db -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | *.pbxuser 16 | *.mode1v3 17 | *.mode2v3 18 | *.perspectivev3 19 | 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | 25 | xcuserdata 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | /Flutter/app.flx 35 | /Flutter/app.zip 36 | /Flutter/App.framework 37 | /Flutter/Flutter.framework 38 | /Flutter/Generated.xcconfig 39 | /Flutter/flutter_assets/ 40 | /ServiceDefinitions.json 41 | 42 | Pods/ 43 | Podfile 44 | Podfile.lock -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | MinimumOSVersion 28 | 8.0 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | if ENV['FLUTTER_FRAMEWORK_DIR'] == nil 5 | abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') 6 | end 7 | 8 | target 'Runner' do 9 | use_frameworks! 10 | 11 | # Pods for Runner 12 | 13 | # Flutter Pods 14 | pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] 15 | 16 | if File.exists? '../.flutter-plugins' 17 | flutter_root = File.expand_path('..') 18 | File.foreach('../.flutter-plugins') { |line| 19 | plugin = line.split(pattern='=') 20 | if plugin.length == 2 21 | name = plugin[0].strip() 22 | path = plugin[1].strip() 23 | resolved_path = File.expand_path("#{path}/ios", flutter_root) 24 | pod name, :path => resolved_path 25 | else 26 | puts "Invalid plugin specification: #{line}" 27 | end 28 | } 29 | end 30 | end 31 | 32 | post_install do |installer| 33 | installer.pods_project.targets.each do |target| 34 | target.build_configurations.each do |config| 35 | config.build_settings['ENABLE_BITCODE'] = 'NO' 36 | end 37 | if target.name == "FMDB" 38 | target.build_configurations.each do |config| 39 | header_search = {"HEADER_SEARCH_PATHS" => "SQLCipher"} 40 | config.build_settings.merge!(header_search) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 7 | [GeneratedPluginRegistrant registerWithRegistry:self]; 8 | // Override point for customization after application launch. 9 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 10 | } 11 | 12 | - (void)applicationWillResignActive:(UIApplication *)application { 13 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 14 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 15 | [super applicationWillResignActive:application]; 16 | } 17 | 18 | - (void)applicationDidEnterBackground:(UIApplication *)application { 19 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 20 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 21 | [super applicationDidEnterBackground:application]; 22 | } 23 | 24 | - (void)applicationWillEnterForeground:(UIApplication *)application { 25 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 26 | [super applicationWillEnterForeground:application]; 27 | } 28 | 29 | - (void)applicationDidBecomeActive:(UIApplication *)application { 30 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 31 | [super applicationDidBecomeActive:application]; 32 | } 33 | 34 | - (void)applicationWillTerminate:(UIApplication *)application { 35 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 36 | [super applicationWillTerminate:application]; 37 | } 38 | 39 | - (BOOL)application:(UIApplication*)application 40 | openURL:(NSURL*)url 41 | sourceApplication:(NSString*)sourceApplication 42 | annotation:(id)annotation { 43 | // Called when the application is asked to open a resource specified by a URL. 44 | return [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; 45 | } 46 | @end 47 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sqflite_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | arm64 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, 8 | NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/lib/database/database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:path/path.dart'; 6 | import 'package:sqflite/sqflite.dart'; 7 | 8 | // return the path 9 | Future initDeleteDb(String dbName) async { 10 | var databasePath = await getDatabasesPath(); 11 | // print(databasePath); 12 | String path = join(databasePath, dbName); 13 | 14 | // make sure the folder exists 15 | if (await new Directory(dirname(path)).exists()) { 16 | await deleteDatabase(path); 17 | } else { 18 | try { 19 | await new Directory(dirname(path)).create(recursive: true); 20 | } catch (e) { 21 | print(e); 22 | } 23 | } 24 | return path; 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/deprecated_test_page.dart: -------------------------------------------------------------------------------- 1 | import 'test_page.dart'; 2 | 3 | class DeprecatedTestPage extends TestPage { 4 | DeprecatedTestPage() : super("Deprecated tests") { 5 | test('None', () async {}); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | //This file is automatically generated. DO NOT EDIT, all your changes would be lost. 7 | class S extends WidgetsLocalizations { 8 | Locale _locale; 9 | String _lang; 10 | 11 | S(this._locale) { 12 | _lang = getLang(_locale); 13 | print('Current locale: $_lang'); 14 | } 15 | 16 | static final GeneratedLocalizationsDelegate delegate = new GeneratedLocalizationsDelegate(); 17 | 18 | static S of(BuildContext context) { 19 | var s = Localizations.of(context, WidgetsLocalizations); 20 | s._lang = getLang(s._locale); 21 | return s; 22 | } 23 | 24 | @override 25 | TextDirection get textDirection => TextDirection.ltr; 26 | } 27 | 28 | class en extends S { 29 | en(Locale locale) : super(locale); 30 | } 31 | 32 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 33 | const GeneratedLocalizationsDelegate(); 34 | 35 | List get supportedLocales { 36 | return [ 37 | new Locale("en", ""), 38 | ]; 39 | } 40 | 41 | LocaleResolutionCallback resolution({Locale fallback}) { 42 | return (Locale locale, Iterable supported) { 43 | var languageLocale = new Locale(locale.languageCode, ""); 44 | if (supported.contains(locale)) 45 | return locale; 46 | else if (supported.contains(languageLocale)) 47 | return languageLocale; 48 | else { 49 | var fallbackLocale = fallback ?? supported.first; 50 | return fallbackLocale; 51 | } 52 | }; 53 | } 54 | 55 | Future load(Locale locale) { 56 | String lang = getLang(locale); 57 | switch (lang) { 58 | case "en": 59 | return new SynchronousFuture(new en(locale)); 60 | default: 61 | return new SynchronousFuture(new S(locale)); 62 | } 63 | } 64 | 65 | bool isSupported(Locale locale) => supportedLocales.contains(locale); 66 | 67 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 68 | } 69 | 70 | String getLang(Locale l) => l.countryCode != null && l.countryCode.isEmpty ? l.languageCode : l.toString(); 71 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | import 'package:sqflite_example/exp_test_page.dart'; 5 | import 'package:sqflite_example/deprecated_test_page.dart'; 6 | import 'model/main_item.dart'; 7 | import 'open_test_page.dart'; 8 | import 'package:sqflite_example/exception_test_page.dart'; 9 | import 'raw_test_page.dart'; 10 | import 'slow_test_page.dart'; 11 | import 'src/main_item_widget.dart'; 12 | import 'type_test_page.dart'; 13 | import 'todo_test_page.dart'; 14 | 15 | void main() { 16 | runApp(new MyApp()); 17 | } 18 | 19 | class MyApp extends StatefulWidget { 20 | // This widget is the root of your application. 21 | 22 | @override 23 | _MyAppState createState() => new _MyAppState(); 24 | } 25 | 26 | const String testRawRoute = "/test/simple"; 27 | const String testOpenRoute = "/test/open"; 28 | const String testSlowRoute = "/test/slow"; 29 | const String testThreadRoute = "/test/thread"; 30 | const String testTodoRoute = "/test/todo"; 31 | const String testExceptionRoute = "/test/exception"; 32 | const String testExpRoute = "/test/exp"; 33 | const String testDeprecatedRoute = "/test/deprecated"; 34 | 35 | class _MyAppState extends State { 36 | var routes = { 37 | '/test': (BuildContext context) => new MyHomePage(), 38 | testRawRoute: (BuildContext context) => new SimpleTestPage(), 39 | testOpenRoute: (BuildContext context) => new OpenTestPage(), 40 | testSlowRoute: (BuildContext context) => new SlowTestPage(), 41 | testTodoRoute: (BuildContext context) => new TodoTestPage(), 42 | testThreadRoute: (BuildContext context) => new TypeTestPage(), 43 | testExceptionRoute: (BuildContext context) => new ExceptionTestPage(), 44 | testExpRoute: (BuildContext context) => new ExpTestPage(), 45 | testDeprecatedRoute: (BuildContext context) => new DeprecatedTestPage(), 46 | }; 47 | @override 48 | Widget build(BuildContext context) { 49 | return new MaterialApp( 50 | title: 'Sqflite Demo', 51 | theme: new ThemeData( 52 | // This is the theme of your application. 53 | // 54 | // Try running your application with "flutter run". You'll see 55 | // the application has a blue toolbar. Then, without quitting 56 | // the app, try changing the primarySwatch below to Colors.green 57 | // and then invoke "hot reload" (press "r" in the console where 58 | // you ran "flutter run", or press Run > Hot Reload App in IntelliJ). 59 | // Notice that the counter didn't reset back to zero -- the application 60 | // is not restarted. 61 | primarySwatch: Colors.blue, 62 | ), 63 | home: new MyHomePage(title: 'Sqflite Demo Home Page'), 64 | routes: routes); 65 | } 66 | } 67 | 68 | class MyHomePage extends StatefulWidget { 69 | final List items = []; 70 | 71 | MyHomePage({Key key, this.title}) : super(key: key) { 72 | items.add(new MainItem("Raw tests", "Raw SQLite operations", 73 | route: testRawRoute)); 74 | items.add(new MainItem("Open tests", "Open onCreate/onUpgrade/onDowngrade", 75 | route: testOpenRoute)); 76 | items.add( 77 | new MainItem("Type tests", "Test value types", route: testThreadRoute)); 78 | items.add( 79 | new MainItem("Slow tests", "Lengthy operations", route: testSlowRoute)); 80 | items.add(new MainItem( 81 | "Todo database example", "Simple Todo-like database usage example", 82 | route: testTodoRoute)); 83 | items.add(new MainItem("Exp tests", "Experimental and various tests", 84 | route: testExpRoute)); 85 | items.add(new MainItem("Exception tests", "Tests that trigger exceptions", 86 | route: testExceptionRoute)); 87 | items.add(new MainItem("Deprecated test", 88 | "Keeping some old tests for deprecated functionalities", 89 | route: testDeprecatedRoute)); 90 | 91 | // Uncomment to view all logs 92 | //Sqflite.devSetDebugModeOn(true); 93 | } 94 | 95 | // This widget is the home page of your application. It is stateful, 96 | // meaning that it has a State object (defined below) that contains 97 | // fields that affect how it looks. 98 | 99 | // This class is the configuration for the state. It holds the 100 | // values (in this case the title) provided by the parent (in this 101 | // case the App widget) and used by the build method of the State. 102 | // Fields in a Widget subclass are always marked "final". 103 | 104 | final String title; 105 | 106 | @override 107 | _MyHomePageState createState() => new _MyHomePageState(); 108 | } 109 | 110 | class _MyHomePageState extends State { 111 | String _platformVersion = 'Unknown'; 112 | 113 | int get _itemCount => widget.items.length; 114 | 115 | @override 116 | initState() { 117 | super.initState(); 118 | initPlatformState(); 119 | } 120 | 121 | // Platform messages are asynchronous, so we initialize in an async method. 122 | initPlatformState() async { 123 | String platformVersion; 124 | // Platform messages may fail, so we use a try/catch PlatformException. 125 | try { 126 | platformVersion = await Sqflite.platformVersion; 127 | } on PlatformException { 128 | platformVersion = "Failed to get platform version"; 129 | } 130 | 131 | // If the widget was removed from the tree while the asynchronous platform 132 | // message was in flight, we want to discard the reply rather than calling 133 | // setState to update our non-existent appearance. 134 | if (!mounted) return; 135 | 136 | setState(() { 137 | _platformVersion = platformVersion; 138 | }); 139 | 140 | print("running on: " + _platformVersion); 141 | } 142 | 143 | @override 144 | Widget build(BuildContext context) { 145 | return new Scaffold( 146 | appBar: new AppBar( 147 | title: new Center( 148 | child: new Text('Sqflite demo', textAlign: TextAlign.center)), 149 | ), 150 | body: new ListView.builder( 151 | itemBuilder: _itemBuilder, itemCount: _itemCount)); 152 | } 153 | 154 | //new Center(child: new Text('Running on: $_platformVersion\n')), 155 | 156 | Widget _itemBuilder(BuildContext context, int index) { 157 | return new MainItemWidget(widget.items[index], (MainItem item) { 158 | Navigator.of(context).pushNamed(item.route); 159 | }); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /example/lib/model/item.dart: -------------------------------------------------------------------------------- 1 | enum ItemState { running, success, failure } 2 | 3 | class Item { 4 | ItemState state = ItemState.running; 5 | Item(this.name); 6 | String name; 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/model/main_item.dart: -------------------------------------------------------------------------------- 1 | class MainItem { 2 | MainItem(this.title, this.description, {this.route}); 3 | String title; 4 | String description; 5 | String route; 6 | } 7 | 8 | class MainRouteItem { 9 | MainRouteItem(this.title, this.description, {this.route}); 10 | String title; 11 | String description; 12 | MainRouteItem route; 13 | } 14 | -------------------------------------------------------------------------------- /example/lib/model/test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class Test { 4 | final bool solo; 5 | final bool skip; 6 | Test(this.name, this.fn, {bool solo, bool skip}) 7 | : solo = solo == true, 8 | skip = skip == true; 9 | String name; 10 | FutureOr Function() fn; 11 | } 12 | -------------------------------------------------------------------------------- /example/lib/slow_test_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | import 'test_page.dart'; 3 | 4 | const String password = 'password'; 5 | 6 | class SlowTestPage extends TestPage { 7 | SlowTestPage() : super("Slow tests") { 8 | test("Perf 100 insert", () async { 9 | String path = await initDeleteDb("slow_txn_100_insert.db"); 10 | Database db = await openDatabase(path, password); 11 | await db.execute("CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)"); 12 | await db.transaction((txn) async { 13 | for (int i = 0; i < 100; i++) { 14 | await txn 15 | .rawInsert("INSERT INTO Test (name) VALUES (?)", ["item $i"]); 16 | } 17 | }); 18 | await db.close(); 19 | }); 20 | 21 | test("Perf 100 insert no txn", () async { 22 | String path = await initDeleteDb("slow_100_insert.db"); 23 | Database db = await openDatabase(path, password); 24 | await db.execute("CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)"); 25 | for (int i = 0; i < 1000; i++) { 26 | await db.rawInsert("INSERT INTO Test (name) VALUES (?)", ["item $i"]); 27 | } 28 | await db.close(); 29 | }); 30 | 31 | test("Perf 1000 insert", () async { 32 | String path = await initDeleteDb("slow_txn_1000_insert.db"); 33 | Database db = await openDatabase(path, password); 34 | await db.execute("CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)"); 35 | 36 | Stopwatch sw = new Stopwatch()..start(); 37 | await db.transaction((txn) async { 38 | for (int i = 0; i < 1000; i++) { 39 | await txn 40 | .rawInsert("INSERT INTO Test (name) VALUES (?)", ["item $i"]); 41 | } 42 | }); 43 | print("1000 insert ${sw.elapsed}"); 44 | await db.close(); 45 | }); 46 | 47 | test("Perf 1000 insert batch", () async { 48 | String path = await initDeleteDb("slow_txn_1000_insert_batch.db"); 49 | Database db = await openDatabase(path, password); 50 | await db.execute("CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)"); 51 | 52 | Stopwatch sw = new Stopwatch()..start(); 53 | Batch batch = db.batch(); 54 | 55 | for (int i = 0; i < 1000; i++) { 56 | batch.rawInsert("INSERT INTO Test (name) VALUES (?)", ["item $i"]); 57 | } 58 | await batch.commit(); 59 | print("1000 insert batch ${sw.elapsed}"); 60 | await db.close(); 61 | }); 62 | 63 | test("Perf 1000 insert batch no result", () async { 64 | String path = 65 | await initDeleteDb("slow_txn_1000_insert_batch_no_result.db"); 66 | Database db = await openDatabase(path, password); 67 | await db.execute("CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)"); 68 | 69 | Stopwatch sw = new Stopwatch()..start(); 70 | Batch batch = db.batch(); 71 | 72 | for (int i = 0; i < 1000; i++) { 73 | batch.rawInsert("INSERT INTO Test (name) VALUES (?)", ["item $i"]); 74 | } 75 | await batch.commit(noResult: true); 76 | 77 | print("1000 insert batch no result ${sw.elapsed}"); 78 | await db.close(); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/lib/src/common_import.dart: -------------------------------------------------------------------------------- 1 | export 'dart:convert'; 2 | export 'package:collection/collection.dart'; 3 | export 'dart:async'; 4 | -------------------------------------------------------------------------------- /example/lib/src/item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import '../model/item.dart'; 4 | 5 | class ItemWidget extends StatefulWidget { 6 | final Item item; 7 | final Function onTap; // = Function(MainItem item); 8 | ItemWidget(this.item, this.onTap); 9 | @override 10 | _ItemWidgetState createState() => new _ItemWidgetState(); 11 | } 12 | 13 | class _ItemWidgetState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | IconData icon; 17 | Color color; 18 | 19 | switch (widget.item.state) { 20 | case ItemState.running: 21 | icon = Icons.more_horiz; 22 | break; 23 | case ItemState.success: 24 | icon = Icons.check; 25 | color = Colors.green; 26 | break; 27 | case ItemState.failure: 28 | icon = Icons.close; 29 | color = Colors.red; 30 | break; 31 | } 32 | return new ListTile( 33 | leading: new IconButton( 34 | icon: new Icon(icon, color: color), 35 | 36 | onPressed: null, // null disables the button 37 | ), 38 | title: new Text(widget.item.name), 39 | onTap: _onTap); 40 | } 41 | 42 | void _onTap() { 43 | widget.onTap(widget.item); 44 | 45 | //print(widget.item.route); 46 | //Navigator.pushNamed(context, widget.item.route); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/lib/src/main_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import '../model/main_item.dart'; 4 | 5 | class MainItemWidget extends StatefulWidget { 6 | final MainItem item; 7 | final Function onTap; // = Function(MainItem item); 8 | MainItemWidget(this.item, this.onTap); 9 | 10 | @override 11 | _MainItemWidgetState createState() => new _MainItemWidgetState(); 12 | } 13 | 14 | class _MainItemWidgetState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return new ListTile( 18 | title: new Text(widget.item.title), 19 | subtitle: new Text(widget.item.description), 20 | onTap: _onTap); 21 | } 22 | 23 | void _onTap() { 24 | widget.onTap(widget.item); 25 | 26 | //print(widget.item.route); 27 | //Navigator.pushNamed(context, widget.item.route); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | export 'dart:async'; 2 | 3 | @deprecated 4 | void devPrint(Object object) { 5 | print(object); 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/test_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | import 'package:sqflite_example/src/common_import.dart'; 7 | import 'model/item.dart'; 8 | import 'model/test.dart'; 9 | import 'src/item_widget.dart'; 10 | export 'package:sqflite_example/database/database.dart'; 11 | 12 | class TestPage extends StatefulWidget { 13 | final String title; 14 | final List tests = []; 15 | 16 | test(String name, FutureOr fn()) { 17 | tests.add(new Test(name, fn)); 18 | } 19 | 20 | @Deprecated("SOLO_TEST - On purpose to remove before checkin") 21 | void solo_test(String name, FutureOr fn()) { 22 | tests.add(new Test(name, fn, solo: true)); 23 | } 24 | 25 | @Deprecated("SKIP_TEST - On purpose to remove before checkin") 26 | void skip_test(String name, FutureOr fn()) { 27 | tests.add(new Test(name, fn, skip: true)); 28 | } 29 | 30 | // Thrown an exception 31 | fail([String message]) { 32 | throw new Exception(message ?? "should fail"); 33 | } 34 | 35 | TestPage(this.title) {} 36 | 37 | @override 38 | _TestPageState createState() => new _TestPageState(); 39 | } 40 | 41 | expect(dynamic value, dynamic expected, {String reason}) { 42 | if (value != expected) { 43 | if (value is List || value is Map) { 44 | if (!const DeepCollectionEquality().equals(value, expected)) { 45 | throw new Exception("collection $value != $expected ${reason ?? ""}"); 46 | } 47 | return; 48 | } 49 | throw new Exception("$value != $expected ${reason ?? ""}"); 50 | } 51 | } 52 | 53 | bool verify(bool condition, [String message]) { 54 | message ??= "verify failed"; 55 | if (condition == null) { 56 | throw new Exception('"$message" null condition'); 57 | } 58 | if (!condition) { 59 | throw new Exception('"$message"'); 60 | } 61 | return condition; 62 | } 63 | 64 | abstract class Group { 65 | List get tests; 66 | 67 | bool _hasSolo; 68 | List _tests = []; 69 | 70 | void add(Test test) { 71 | if (!test.skip) { 72 | if (test.solo) { 73 | if (_hasSolo != true) { 74 | _hasSolo = true; 75 | _tests.clear(); 76 | } 77 | _tests.add(test); 78 | } else if (_hasSolo != true) { 79 | _tests.add(test); 80 | } 81 | } 82 | } 83 | 84 | bool get hasSolo => _hasSolo; 85 | } 86 | 87 | class _TestPageState extends State with Group { 88 | int get _itemCount => items.length; 89 | 90 | List items = []; 91 | 92 | _run() async { 93 | if (!mounted) { 94 | return null; 95 | } 96 | 97 | setState(() { 98 | items.clear(); 99 | }); 100 | _tests.clear(); 101 | for (Test test in widget.tests) { 102 | add(test); 103 | } 104 | for (Test test in _tests) { 105 | Item item = new Item("${test.name}"); 106 | 107 | int position; 108 | setState(() { 109 | position = items.length; 110 | items.add(item); 111 | }); 112 | try { 113 | await test.fn(); 114 | 115 | item = new Item("${test.name}")..state = ItemState.success; 116 | } catch (e) { 117 | print(e); 118 | item = new Item("${test.name}")..state = ItemState.failure; 119 | } 120 | 121 | if (!mounted) { 122 | return null; 123 | } 124 | 125 | setState(() { 126 | items[position] = item; 127 | }); 128 | } 129 | } 130 | 131 | _runTest(int index) async { 132 | if (!mounted) { 133 | return null; 134 | } 135 | 136 | Test test = _tests[index]; 137 | 138 | Item item = items[index]; 139 | setState(() { 140 | item.state = ItemState.running; 141 | }); 142 | try { 143 | print("TEST Running ${test.name}"); 144 | await test.fn(); 145 | print("TEST Done ${test.name}"); 146 | 147 | item = new Item("${test.name}")..state = ItemState.success; 148 | } catch (e, st) { 149 | print("TEST Error $e running ${test.name}"); 150 | try { 151 | //print(st); 152 | if (await Sqflite.getDebugModeOn()) { 153 | print(st); 154 | } 155 | } catch (_) {} 156 | item = new Item("${test.name}")..state = ItemState.failure; 157 | } 158 | 159 | if (!mounted) { 160 | return null; 161 | } 162 | 163 | setState(() { 164 | items[index] = item; 165 | }); 166 | } 167 | 168 | @override 169 | initState() { 170 | super.initState(); 171 | /* 172 | setState(() { 173 | _itemCount = 3; 174 | }); 175 | */ 176 | _run(); 177 | } 178 | 179 | @override 180 | Widget build(BuildContext context) { 181 | return new Scaffold( 182 | appBar: new AppBar(title: new Text(widget.title), actions: [ 183 | new IconButton( 184 | icon: new Icon(Icons.refresh), 185 | tooltip: 'Run again', 186 | onPressed: _run, 187 | ), 188 | ]), 189 | body: new ListView.builder( 190 | itemBuilder: _itemBuilder, itemCount: _itemCount)); 191 | } 192 | 193 | Widget _itemBuilder(BuildContext context, int index) { 194 | Item item = getItem(index); 195 | return new ItemWidget(item, (Item item) { 196 | //Navigator.of(context).pushNamed(item.route); 197 | _runTest(index); 198 | }); 199 | } 200 | 201 | Item getItem(int index) { 202 | return items[index]; 203 | } 204 | 205 | @override 206 | List get tests => widget.tests; 207 | } 208 | -------------------------------------------------------------------------------- /example/lib/todo_test_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:sqflite/sqflite.dart'; 3 | import 'test_page.dart'; 4 | 5 | final String tableTodo = "todo"; 6 | final String columnId = "_id"; 7 | final String columnTitle = "title"; 8 | final String columnDone = "done"; 9 | 10 | const String password = 'password'; 11 | 12 | class Todo { 13 | int id; 14 | String title; 15 | bool done; 16 | 17 | Map toMap() { 18 | var map = { 19 | columnTitle: title, 20 | columnDone: done == true ? 1 : 0 21 | }; 22 | if (id != null) { 23 | map[columnId] = id; 24 | } 25 | return map; 26 | } 27 | 28 | Todo(); 29 | 30 | Todo.fromMap(Map map) { 31 | id = map[columnId] as int; 32 | title = map[columnTitle] as String; 33 | done = map[columnDone] == 1; 34 | } 35 | } 36 | 37 | class TodoProvider { 38 | Database db; 39 | 40 | Future open(String path) async { 41 | db = await openDatabase(path, password, version: 1, 42 | onCreate: (Database db, int version) async { 43 | await db.execute(''' 44 | create table $tableTodo ( 45 | $columnId integer primary key autoincrement, 46 | $columnTitle text not null, 47 | $columnDone integer not null) 48 | '''); 49 | }); 50 | } 51 | 52 | Future insert(Todo todo) async { 53 | todo.id = await db.insert(tableTodo, todo.toMap()); 54 | return todo; 55 | } 56 | 57 | Future getTodo(int id) async { 58 | List maps = await db.query(tableTodo, 59 | columns: [columnId, columnDone, columnTitle], 60 | where: "$columnId = ?", 61 | whereArgs: [id]); 62 | if (maps.length > 0) { 63 | return new Todo.fromMap(maps.first); 64 | } 65 | return null; 66 | } 67 | 68 | Future delete(int id) async { 69 | return await db.delete(tableTodo, where: "$columnId = ?", whereArgs: [id]); 70 | } 71 | 72 | Future update(Todo todo) async { 73 | return await db.update(tableTodo, todo.toMap(), 74 | where: "$columnId = ?", whereArgs: [todo.id]); 75 | } 76 | 77 | Future close() async => db.close(); 78 | } 79 | 80 | class TodoTestPage extends TestPage { 81 | TodoTestPage() : super("Todo example") { 82 | test("open", () async { 83 | // await Sqflite.devSetDebugModeOn(true); 84 | String path = await initDeleteDb("simple_todo_open.db"); 85 | TodoProvider todoProvider = new TodoProvider(); 86 | await todoProvider.open(path); 87 | 88 | await todoProvider.close(); 89 | //await Sqflite.setDebugModeOn(false); 90 | }); 91 | 92 | test("insert/query/update/delete", () async { 93 | // await Sqflite.devSetDebugModeOn(); 94 | String path = await initDeleteDb("simple_todo.db"); 95 | TodoProvider todoProvider = new TodoProvider(); 96 | await todoProvider.open(path); 97 | 98 | Todo todo = new Todo()..title = "test"; 99 | await todoProvider.insert(todo); 100 | expect(todo.id, 1); 101 | 102 | expect(await todoProvider.getTodo(0), null); 103 | todo = await todoProvider.getTodo(1); 104 | expect(todo.id, 1); 105 | expect(todo.title, "test"); 106 | expect(todo.done, false); 107 | 108 | todo.done = true; 109 | expect(await todoProvider.update(todo), 1); 110 | todo = await todoProvider.getTodo(1); 111 | expect(todo.id, 1); 112 | expect(todo.title, "test"); 113 | expect(todo.done, true); 114 | 115 | expect(await todoProvider.delete(0), 0); 116 | expect(await todoProvider.delete(1), 1); 117 | expect(await todoProvider.getTodo(1), null); 118 | 119 | await todoProvider.close(); 120 | //await Sqflite.setDebugModeOn(false); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /example/lib/type_test_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:sqflite/sqflite.dart'; 6 | 7 | import 'test_page.dart'; 8 | 9 | const String password = 'password'; 10 | 11 | class _Data { 12 | Database db; 13 | } 14 | 15 | class TypeTestPage extends TestPage { 16 | final _Data data = new _Data(); 17 | 18 | // Get the value field from a given 19 | Future getValue(int id) async { 20 | return ((await data.db.query("Test", where: "_id = $id")).first)["value"]; 21 | } 22 | 23 | // insert the value field and return the id 24 | Future insertValue(dynamic value) async { 25 | return await data.db.insert("Test", {"value": value}); 26 | } 27 | 28 | // insert the value field and return the id 29 | Future updateValue(int id, dynamic value) async { 30 | return await data.db.update("Test", {"value": value}, where: "_id = $id"); 31 | } 32 | 33 | TypeTestPage() : super("Type tests") { 34 | test("int", () async { 35 | //await Sqflite.devSetDebugModeOn(true); 36 | String path = await initDeleteDb("type_int.db"); 37 | data.db = await openDatabase(path, password, version: 1, onCreate: (Database db, int version) async { 38 | await db.execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value INTEGER)"); 39 | }); 40 | int id = await insertValue(-1); 41 | expect(await getValue(id), -1); 42 | 43 | // less than 32 bits 44 | id = await insertValue(pow(2, 31)); 45 | expect(await getValue(id), pow(2, 31)); 46 | 47 | // more than 32 bits 48 | id = await insertValue(pow(2, 33)); 49 | //devPrint("2^33: ${await getValue(id)}"); 50 | expect(await getValue(id), pow(2, 33)); 51 | 52 | id = await insertValue(pow(2, 62)); 53 | //devPrint("2^62: ${pow(2, 62)} ${await getValue(id)}"); 54 | expect(await getValue(id), pow(2, 62), reason: "2^62: ${pow(2, 62)} ${await getValue(id)}"); 55 | 56 | int value = pow(2, 63) - 1; 57 | id = await insertValue(value); 58 | //devPrint("${value} ${await getValue(id)}"); 59 | expect(await getValue(id), value, reason: "${value} ${await getValue(id)}"); 60 | 61 | value = -(pow(2, 63)).round(); 62 | id = await insertValue(value); 63 | //devPrint("${value} ${await getValue(id)}"); 64 | expect(await getValue(id), value, reason: "${value} ${await getValue(id)}"); 65 | /* 66 | id = await insertValue(pow(2, 63)); 67 | devPrint("2^63: ${pow(2, 63)} ${await getValue(id)}"); 68 | assert(await getValue(id) == pow(2, 63), "2^63: ${pow(2, 63)} ${await getValue(id)}"); 69 | 70 | // more then 64 bits 71 | id = await insertValue(pow(2, 65)); 72 | assert(await getValue(id) == pow(2, 65)); 73 | 74 | // more then 128 bits 75 | id = await insertValue(pow(2, 129)); 76 | assert(await getValue(id) == pow(2, 129)); 77 | */ 78 | await data.db.close(); 79 | }); 80 | 81 | test("real", () async { 82 | //await Sqflite.devSetDebugModeOn(true); 83 | String path = await initDeleteDb("type_real.db"); 84 | data.db = await openDatabase(path, password, version: 1, onCreate: (Database db, int version) async { 85 | await db.execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value REAL)"); 86 | }); 87 | int id = await insertValue(-1.1); 88 | expect(await getValue(id), -1.1); 89 | // big float 90 | id = await insertValue(1 / 3); 91 | expect(await getValue(id), 1 / 3); 92 | id = await insertValue(pow(2, 63) + .1); 93 | expect(await getValue(id), pow(2, 63) + 0.1); 94 | 95 | // integer? 96 | id = await insertValue(pow(2, 62)); 97 | expect(await getValue(id), pow(2, 62)); 98 | await data.db.close(); 99 | }); 100 | 101 | test("text", () async { 102 | //await Sqflite.devSetDebugModeOn(true); 103 | String path = await initDeleteDb("type_text.db"); 104 | data.db = await openDatabase(path, password, version: 1, onCreate: (Database db, int version) async { 105 | await db.execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value TEXT)"); 106 | }); 107 | int id = await insertValue("simple text"); 108 | expect(await getValue(id), "simple text"); 109 | // null 110 | id = await insertValue(null); 111 | expect(await getValue(id), null); 112 | 113 | // utf-8 114 | id = await insertValue("àöé"); 115 | expect(await getValue(id), "àöé"); 116 | 117 | await data.db.close(); 118 | }); 119 | 120 | test("blob", () async { 121 | //await Sqflite.devSetDebugModeOn(true); 122 | String path = await initDeleteDb("type_blob.db"); 123 | data.db = await openDatabase(path, password, version: 1, onCreate: (Database db, int version) async { 124 | await db.execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value BLOB)"); 125 | }); 126 | try { 127 | // insert text in blob 128 | int id = await insertValue("simple text"); 129 | expect(await getValue(id), "simple text"); 130 | 131 | // UInt8List - default 132 | ByteData byteData = new ByteData(1); 133 | byteData.setInt8(0, 1); 134 | id = await insertValue(byteData.buffer.asUint8List()); 135 | //print(await getValue(id)); 136 | expect(await getValue(id), [1]); 137 | 138 | // empty array not supported 139 | //id = await insertValue([]); 140 | //print(await getValue(id)); 141 | //assert(eq.equals(await getValue(id), [])); 142 | 143 | id = await insertValue([1, 2, 3, 4]); 144 | //print(await getValue(id)); 145 | expect(await getValue(id), [1, 2, 3, 4], reason: "${await getValue(id)}"); 146 | } finally { 147 | await data.db.close(); 148 | } 149 | }); 150 | 151 | test("null", () async { 152 | // await Sqflite.devSetDebugModeOn(true); 153 | String path = await initDeleteDb("type_null.db"); 154 | data.db = await openDatabase(path, "password", version: 1, 155 | onCreate: (Database db, int version) async { 156 | await db 157 | .execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value TEXT)"); 158 | }); 159 | try { 160 | int id = await insertValue(null); 161 | expect(await getValue(id), null); 162 | 163 | // Make a string 164 | expect(await updateValue(id, "dummy"), 1); 165 | expect(await getValue(id), "dummy"); 166 | 167 | expect(await updateValue(id, null), 1); 168 | expect(await getValue(id), null); 169 | } finally { 170 | await data.db.close(); 171 | } 172 | }); 173 | 174 | test("date_time", () async { 175 | // await Sqflite.devSetDebugModeOn(true); 176 | String path = await initDeleteDb("type_date_time.db"); 177 | data.db = await openDatabase(path, 'password', version: 1, 178 | onCreate: (Database db, int version) async { 179 | await db 180 | .execute("CREATE TABLE Test (_id INTEGER PRIMARY KEY, value TEXT)"); 181 | }); 182 | try { 183 | bool failed = false; 184 | try { 185 | await insertValue( 186 | new DateTime.fromMillisecondsSinceEpoch(1234567890)); 187 | } on ArgumentError catch (_) { 188 | failed = true; 189 | } 190 | expect(failed, true); 191 | } finally { 192 | await data.db.close(); 193 | } 194 | }); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sqflite_example 2 | description: Demonstrates how to use the sqflite plugin. 3 | 4 | environment: 5 | sdk: ">=2.0.0-dev.35 <3.0.0" 6 | 7 | dependencies: 8 | path: any 9 | collection: any 10 | flutter: 11 | sdk: flutter 12 | sqflite: 13 | path: ../ 14 | 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://www.dartlang.org/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | 25 | # The following line ensures that the Material Icons font is 26 | # included with your application, so that you can use the icons in 27 | # the Icons class. 28 | uses-material-design: true 29 | 30 | # To add assets to your application, add an assets section here, in 31 | # this "flutter" section, as in: 32 | # assets: 33 | # - images/a_dot_burr.jpeg 34 | # - images/a_dot_ham.jpeg 35 | assets: 36 | - assets/example.db 37 | - assets/issue_64.db 38 | 39 | # dev only 40 | # dependency_overrides: 41 | # synchronized: 42 | # path: ../../synchronized.dart -------------------------------------------------------------------------------- /example/test/database_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | main() { 4 | group("database", () { 5 | test("open", () async { 6 | // Can't do unit test flutter plugin yet 7 | }); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /example/test/model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqflite_example/model/test.dart'; 3 | 4 | main() { 5 | group("model", () { 6 | test("test_sync", () async { 7 | bool ran = false; 8 | Test test = new Test("test", () { 9 | ran = true; 10 | }); 11 | await test.fn(); 12 | expect(ran, isTrue); 13 | }); 14 | 15 | test("test_async", () async { 16 | bool ran = false; 17 | Test test = new Test("test", () async { 18 | ran = true; 19 | }); 20 | await test.fn(); 21 | expect(ran, isTrue); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | 13 | *.pbxuser 14 | *.mode1v3 15 | *.mode2v3 16 | *.perspectivev3 17 | 18 | !default.pbxuser 19 | !default.mode1v3 20 | !default.mode2v3 21 | !default.perspectivev3 22 | 23 | xcuserdata 24 | 25 | *.moved-aside 26 | 27 | *.pyc 28 | *sync/ 29 | Icon? 30 | .tags* 31 | 32 | Pods/ 33 | Podfile -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwilApp/encrypted_sqlite/fbfeec7272f2d3416a118c2af7993a2a23849774/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Operation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Operation.h 3 | // sqflite 4 | // 5 | // Created by Alexandre Roux on 09/01/2018. 6 | // 7 | #import 8 | 9 | #ifndef Operation_h 10 | #define Operation_h 11 | 12 | @interface Operation : NSObject 13 | 14 | - (NSString*)getMethod; 15 | - (NSString*)getSql; 16 | - (NSArray*)getSqlArguments; 17 | - (void)success:(NSObject*)results; 18 | - (void)error:(NSObject*)error; 19 | - (bool)getNoResult; 20 | 21 | @end 22 | 23 | @interface BatchOperation : Operation 24 | 25 | @property (atomic, retain) NSDictionary* dictionary; 26 | @property (atomic, retain) NSObject* results; 27 | @property (atomic, retain) NSObject* error; 28 | @property (atomic, assign) bool noResult; 29 | 30 | - (void)handleSuccess:(NSMutableArray*)results; 31 | - (void)handleError:(FlutterResult)result; 32 | 33 | @end 34 | 35 | @interface MethodCallOperation : Operation 36 | 37 | @property (atomic, retain) FlutterMethodCall* flutterMethodCall; 38 | @property (atomic, assign) FlutterResult flutterResult; 39 | 40 | + (MethodCallOperation*)newWithCall:(FlutterMethodCall*)flutterMethodCall result:(FlutterResult)flutterResult; 41 | 42 | @end 43 | 44 | #endif /* Operation_h */ 45 | -------------------------------------------------------------------------------- /ios/Classes/Operation.m: -------------------------------------------------------------------------------- 1 | // 2 | // Operation.m 3 | // sqflite 4 | // 5 | // Created by Alexandre Roux on 09/01/2018. 6 | // 7 | 8 | #import 9 | #import "Operation.h" 10 | #import "SqflitePlugin.h" 11 | 12 | // Abstract 13 | @implementation Operation 14 | 15 | - (NSString*)getMethod { 16 | return nil; 17 | } 18 | - (NSString*)getSql { 19 | return nil; 20 | } 21 | - (NSArray*)getSqlArguments { 22 | return nil; 23 | } 24 | - (bool)getNoResult { 25 | return false; 26 | } 27 | - (void)success:(NSObject*)results {} 28 | 29 | - (void)error:(NSObject*)error {} 30 | 31 | @end 32 | 33 | @implementation BatchOperation 34 | 35 | @synthesize dictionary, results, error, noResult; 36 | 37 | - (NSString*)getMethod { 38 | return [dictionary objectForKey:_paramMethod]; 39 | } 40 | 41 | - (NSString*)getSql { 42 | return [dictionary objectForKey:_paramSql]; 43 | } 44 | 45 | - (NSArray*)getSqlArguments { 46 | NSArray* arguments = [dictionary objectForKey:_paramSqlArguments]; 47 | return [SqflitePlugin toSqlArguments:arguments]; 48 | } 49 | 50 | - (bool)getNoResult { 51 | return noResult; 52 | } 53 | 54 | - (void)success:(NSObject*)results { 55 | self.results = results; 56 | } 57 | - (void)error:(NSObject*)error { 58 | self.error = error; 59 | } 60 | 61 | - (void)handleSuccess:(NSMutableArray*)results { 62 | if (![self getNoResult]) { 63 | [results addObject:((self.results == nil) ? [NSNull null] : self.results)]; 64 | } 65 | } 66 | - (void)handleError:(FlutterResult)result { 67 | result(error); 68 | } 69 | 70 | @end 71 | 72 | @implementation MethodCallOperation 73 | 74 | @synthesize flutterMethodCall; 75 | @synthesize flutterResult; 76 | 77 | + (MethodCallOperation*)newWithCall:(FlutterMethodCall*)flutterMethodCall result:(FlutterResult)flutterResult { 78 | MethodCallOperation* operation = [MethodCallOperation new]; 79 | operation.flutterMethodCall = flutterMethodCall; 80 | operation.flutterResult = flutterResult; 81 | return operation; 82 | } 83 | 84 | - (NSString*)getMethod { 85 | return flutterMethodCall.method; 86 | } 87 | 88 | - (NSString*)getSql { 89 | return flutterMethodCall.arguments[_paramSql]; 90 | } 91 | 92 | - (bool)getNoResult { 93 | NSNumber* noResult = flutterMethodCall.arguments[_paramNoResult]; 94 | return [noResult boolValue]; 95 | } 96 | 97 | - (NSArray*)getSqlArguments { 98 | NSArray* arguments = flutterMethodCall.arguments[_paramSqlArguments]; 99 | return [SqflitePlugin toSqlArguments:arguments]; 100 | } 101 | 102 | - (void)success:(NSObject*)results { 103 | flutterResult(results); 104 | } 105 | - (void)error:(NSObject*)error { 106 | flutterResult(error); 107 | } 108 | @end 109 | -------------------------------------------------------------------------------- /ios/Classes/SqflitePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SqflitePlugin : NSObject 4 | 5 | + (NSArray*)toSqlArguments:(NSArray*)rawArguments; 6 | 7 | @end 8 | 9 | extern NSString *const _paramMethod; 10 | extern NSString *const _paramSql; 11 | extern NSString *const _paramSqlArguments; 12 | extern NSString *const _paramNoResult; 13 | -------------------------------------------------------------------------------- /ios/sqflite.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'sqflite' 6 | s.version = '0.0.1' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'FMDB/SQLCipher' 19 | 20 | s.ios.deployment_target = '8.0' 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/sql.dart: -------------------------------------------------------------------------------- 1 | // Only export: 2 | // * [ConflictAlgorithm] 3 | // * [escapeName] 4 | // * [unescapeName] 5 | export 'src/sql_builder.dart' show ConflictAlgorithm, escapeName, unescapeName; 6 | -------------------------------------------------------------------------------- /lib/src/batch.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | import 'package:sqflite/src/constant.dart'; 3 | import 'package:sqflite/src/database.dart'; 4 | import 'package:sqflite/src/sql_builder.dart'; 5 | import 'package:sqflite/src/transaction.dart'; 6 | import 'package:sqflite/src/utils.dart'; 7 | 8 | abstract class SqfliteBatch implements Batch { 9 | final List> operations = []; 10 | 11 | @override 12 | Future> commit({bool exclusive, bool noResult}) => 13 | apply(exclusive: exclusive, noResult: noResult); 14 | 15 | void _add(String method, String sql, List arguments) { 16 | operations.add({ 17 | paramMethod: method, 18 | paramSql: sql, 19 | paramSqlArguments: arguments 20 | }); 21 | } 22 | 23 | @override 24 | void rawInsert(String sql, [List arguments]) { 25 | _add(methodInsert, sql, arguments); 26 | } 27 | 28 | @override 29 | void insert(String table, Map values, 30 | {String nullColumnHack, ConflictAlgorithm conflictAlgorithm}) { 31 | SqlBuilder builder = new SqlBuilder.insert(table, values, 32 | nullColumnHack: nullColumnHack, conflictAlgorithm: conflictAlgorithm); 33 | return rawInsert(builder.sql, builder.arguments); 34 | } 35 | 36 | @override 37 | void rawQuery(String sql, [List arguments]) { 38 | _add(methodQuery, sql, arguments); 39 | } 40 | 41 | @override 42 | void query(String table, 43 | {bool distinct, 44 | List columns, 45 | String where, 46 | List whereArgs, 47 | String groupBy, 48 | String having, 49 | String orderBy, 50 | int limit, 51 | int offset}) { 52 | SqlBuilder builder = new SqlBuilder.query(table, 53 | distinct: distinct, 54 | columns: columns, 55 | where: where, 56 | whereArgs: whereArgs, 57 | groupBy: groupBy, 58 | having: having, 59 | orderBy: orderBy, 60 | limit: limit, 61 | offset: offset); 62 | return rawQuery(builder.sql, builder.arguments); 63 | } 64 | 65 | @override 66 | void rawUpdate(String sql, [List arguments]) { 67 | _add(methodUpdate, sql, arguments); 68 | } 69 | 70 | @override 71 | void update(String table, Map values, 72 | {String where, List whereArgs, ConflictAlgorithm conflictAlgorithm}) { 73 | SqlBuilder builder = new SqlBuilder.update(table, values, 74 | where: where, 75 | whereArgs: whereArgs, 76 | conflictAlgorithm: conflictAlgorithm); 77 | return rawUpdate(builder.sql, builder.arguments); 78 | } 79 | 80 | @override 81 | void delete(String table, {String where, List whereArgs}) { 82 | SqlBuilder builder = 83 | new SqlBuilder.delete(table, where: where, whereArgs: whereArgs); 84 | return rawDelete(builder.sql, builder.arguments); 85 | } 86 | 87 | @override 88 | void rawDelete(String sql, [List arguments]) { 89 | rawUpdate(sql, arguments); 90 | } 91 | 92 | @override 93 | void execute(String sql, [List arguments]) { 94 | _add(methodExecute, sql, arguments); 95 | } 96 | 97 | @override 98 | Future apply({bool exclusive, bool noResult}) => 99 | commit(exclusive: exclusive, noResult: noResult); 100 | } 101 | 102 | class SqfliteDatabaseBatch extends SqfliteBatch { 103 | SqfliteDatabaseBatch(this.database); 104 | 105 | final SqfliteDatabase database; 106 | 107 | @override 108 | Future commit({bool exclusive, bool noResult}) { 109 | return database.transaction((txn) { 110 | return database.txnApplyBatch(txn as SqfliteTransaction, this, 111 | noResult: noResult); 112 | }, exclusive: exclusive); 113 | } 114 | } 115 | 116 | class SqfliteTransactionBatch extends SqfliteBatch { 117 | final SqfliteTransaction transaction; 118 | 119 | SqfliteTransactionBatch(this.transaction); 120 | 121 | @override 122 | Future commit({bool exclusive, bool noResult}) { 123 | if (exclusive != null) { 124 | throw new ArgumentError.value(exclusive, "exclusive", 125 | "must not be set when commiting a batch in a transaction"); 126 | } 127 | return transaction.database 128 | .txnApplyBatch(transaction, this, noResult: noResult); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/src/constant.dart: -------------------------------------------------------------------------------- 1 | // Method to use 2 | const String methodInsert = "insert"; 3 | const String methodBatch = "batch"; 4 | const String methodSetDebugModeOn = "debugMode"; 5 | const String methodOptions = "options"; 6 | const String methodCloseDatabase = "closeDatabase"; 7 | const String methodOpenDatabase = "openDatabase"; 8 | const String methodExecute = "execute"; 9 | const String methodUpdate = "update"; 10 | const String methodQuery = "query"; 11 | const String methodGetPlatformVersion = "getPlatformVersion"; 12 | const String methodGetDatabasesPath = "getDatabasesPath"; 13 | 14 | // For batch 15 | const String paramOperations = "operations"; 16 | // if true the result of each batch operation is not filled 17 | const String paramNoResult = "noResult"; 18 | // For each operation 19 | const String paramMethod = "method"; 20 | 21 | // The database path (string) 22 | const String paramPath = "path"; 23 | const String paramPassword = "password"; 24 | // The database version (int) 25 | const String paramVersion = "version"; 26 | // The database id (int) 27 | const String paramId = "id"; 28 | // When opening the database (bool) 29 | const String paramReadOnly = "readOnly"; 30 | 31 | const String paramTable = "table"; 32 | const String paramValues = "values"; 33 | 34 | // for SQL query 35 | const String paramSql = "sql"; 36 | const String paramSqlArguments = "arguments"; 37 | 38 | // Error 39 | const String sqliteErrorCode = "sqlite_error"; 40 | 41 | const String inMemoryDatabasePath = ":memory:"; 42 | 43 | // Non final for changing it during testing 44 | // If a database called is delayed by this duration, a print will happen 45 | const Duration lockWarningDuration = const Duration(seconds: 10); 46 | -------------------------------------------------------------------------------- /lib/src/database_factory.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:path/path.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | import 'package:sqflite/src/constant.dart'; 7 | import 'package:sqflite/src/database.dart'; 8 | import 'package:sqflite/src/exception.dart'; 9 | import 'package:synchronized/synchronized.dart'; 10 | 11 | import 'sqflite_impl.dart' as impl; 12 | 13 | SqfliteDatabaseFactory _databaseFactory; 14 | 15 | DatabaseFactory get databaseFactory => sqlfliteDatabaseFactory; 16 | 17 | SqfliteDatabaseFactory get sqlfliteDatabaseFactory => 18 | _databaseFactory ??= new SqfliteDatabaseFactory(); 19 | 20 | Future openReadOnlyDatabase(String path, String password) async { 21 | var options = new SqfliteOpenDatabaseOptions(readOnly: true); 22 | return sqlfliteDatabaseFactory.openDatabase(path, password, options: options); 23 | } 24 | 25 | abstract class DatabaseFactory { 26 | Future openDatabase(String path, String password, 27 | {OpenDatabaseOptions options}); 28 | 29 | Future getDatabasesPath(); 30 | 31 | Future deleteDatabase(String path); 32 | } 33 | 34 | /// 35 | /// Options to open a database 36 | /// See [openDatabase] for details 37 | /// 38 | class SqfliteOpenDatabaseOptions implements OpenDatabaseOptions { 39 | SqfliteOpenDatabaseOptions({ 40 | this.version, 41 | this.onConfigure, 42 | this.onCreate, 43 | this.onUpgrade, 44 | this.onDowngrade, 45 | this.onOpen, 46 | this.readOnly = false, 47 | this.singleInstance = true, 48 | }) { 49 | readOnly ??= false; 50 | singleInstance ??= true; 51 | } 52 | 53 | @override 54 | int version; 55 | @override 56 | OnDatabaseConfigureFn onConfigure; 57 | @override 58 | OnDatabaseCreateFn onCreate; 59 | @override 60 | OnDatabaseVersionChangeFn onUpgrade; 61 | @override 62 | OnDatabaseVersionChangeFn onDowngrade; 63 | @override 64 | OnDatabaseOpenFn onOpen; 65 | @override 66 | bool readOnly; 67 | @override 68 | bool singleInstance; 69 | } 70 | 71 | class SqfliteDatabaseFactory implements DatabaseFactory { 72 | // for single instances only 73 | Map databaseOpenHelpers = {}; 74 | SqfliteDatabaseOpenHelper nullDatabaseOpenHelper; 75 | 76 | // to allow mock overriding 77 | Future invokeMethod(String method, [dynamic arguments]) => 78 | impl.invokeMethod(method, arguments); 79 | 80 | // open lock mechanism 81 | var lock = new Lock(); 82 | 83 | SqfliteDatabase newDatabase( 84 | SqfliteDatabaseOpenHelper openHelper, String path, String password) => 85 | new SqfliteDatabase(openHelper, path, password); 86 | 87 | // internal close 88 | void doCloseDatabase(SqfliteDatabase database) { 89 | if (database?.options?.singleInstance == true) { 90 | _removeDatabaseOpenHelper(database.path); 91 | } 92 | } 93 | 94 | void _removeDatabaseOpenHelper(String path) { 95 | if (path == null) { 96 | nullDatabaseOpenHelper = null; 97 | } else { 98 | databaseOpenHelpers.remove(path); 99 | } 100 | } 101 | 102 | @override 103 | Future openDatabase(String path, String password, 104 | {OpenDatabaseOptions options}) async { 105 | options ??= new SqfliteOpenDatabaseOptions(); 106 | 107 | if (options?.singleInstance == true) { 108 | SqfliteDatabaseOpenHelper getExistingDatabaseOpenHelper(String path) { 109 | if (path != null) { 110 | return databaseOpenHelpers[path]; 111 | } else { 112 | return nullDatabaseOpenHelper; 113 | } 114 | } 115 | 116 | void setDatabaseOpenHelper(SqfliteDatabaseOpenHelper helper) { 117 | if (path == null) { 118 | nullDatabaseOpenHelper = helper; 119 | } else { 120 | if (helper == null) { 121 | databaseOpenHelpers.remove(path); 122 | } else { 123 | databaseOpenHelpers[path] = helper; 124 | } 125 | } 126 | } 127 | 128 | if (path != null && path != inMemoryDatabasePath) { 129 | path = absolute(normalize(path)); 130 | } 131 | var databaseOpenHelper = getExistingDatabaseOpenHelper(path); 132 | 133 | bool firstOpen = databaseOpenHelper == null; 134 | if (firstOpen) { 135 | databaseOpenHelper = 136 | new SqfliteDatabaseOpenHelper(this, path, password, options); 137 | setDatabaseOpenHelper(databaseOpenHelper); 138 | } 139 | try { 140 | return await databaseOpenHelper.openDatabase(); 141 | } catch (e) { 142 | // If first open fail remove the reference 143 | if (firstOpen) { 144 | _removeDatabaseOpenHelper(path); 145 | } 146 | rethrow; 147 | } 148 | } else { 149 | var databaseOpenHelper = 150 | new SqfliteDatabaseOpenHelper(this, path, password, options); 151 | return await databaseOpenHelper.openDatabase(); 152 | } 153 | } 154 | 155 | @override 156 | Future deleteDatabase(String path) async { 157 | try { 158 | await new File(path).delete(recursive: true); 159 | } catch (_e) { 160 | // 0.8.4 161 | // print(e); 162 | } 163 | } 164 | 165 | @override 166 | Future getDatabasesPath() async { 167 | var path = await wrapDatabaseException(() { 168 | return invokeMethod(methodGetDatabasesPath); 169 | }); 170 | if (path == null) { 171 | throw new SqfliteDatabaseException("getDatabasesPath is null", null); 172 | } 173 | return path; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/src/exception.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:sqflite/src/constant.dart'; 5 | 6 | // Wrap sqlite native exception 7 | abstract class DatabaseException implements Exception { 8 | String _message; 9 | 10 | DatabaseException(this._message); 11 | 12 | @override 13 | String toString() => "DatabaseException($_message)"; 14 | 15 | bool isNoSuchTableError([String table]) { 16 | if (_message != null) { 17 | String expected = "no such table: "; 18 | if (table != null) { 19 | expected += table; 20 | } 21 | return _message.contains(expected); 22 | } 23 | return false; 24 | } 25 | 26 | bool isSyntaxError() { 27 | if (_message != null) { 28 | return _message.contains("syntax error"); 29 | } 30 | return false; 31 | } 32 | 33 | bool isOpenFailedError() { 34 | if (_message != null) { 35 | return _message.startsWith("open_failed"); 36 | } 37 | return false; 38 | } 39 | 40 | bool isDatabaseClosedError() { 41 | if (_message != null) { 42 | return _message.startsWith("database_closed"); 43 | } 44 | return false; 45 | } 46 | 47 | bool isReadOnlyError() { 48 | if (_message != null) { 49 | return _message.contains("readonly"); 50 | } 51 | return false; 52 | } 53 | 54 | bool isUniqueConstraintError([String field]) { 55 | if (_message != null) { 56 | String expected = "UNIQUE constraint failed: "; 57 | if (field != null) { 58 | expected += field; 59 | } 60 | return _message.toLowerCase().contains(expected.toLowerCase()); 61 | } 62 | return false; 63 | } 64 | } 65 | 66 | class SqfliteDatabaseException extends DatabaseException { 67 | dynamic result; 68 | 69 | SqfliteDatabaseException(String message, this.result) : super(message); 70 | 71 | @override 72 | String toString() { 73 | if (result is Map) { 74 | if (result[paramSql] != null) { 75 | dynamic args = result[paramSqlArguments]; 76 | if (args == null) { 77 | return "DatabaseException($_message) sql '${result[paramSql]}'"; 78 | } else { 79 | return "DatabaseException($_message) sql '${result[paramSql]}' args ${args}}"; 80 | } 81 | } 82 | } 83 | return super.toString(); 84 | } 85 | 86 | /// Parse the sqlite native message to extract the code 87 | /// See https://www.sqlite.org/rescode.html for the list of result code 88 | int getResultCode() { 89 | String message = _message.toLowerCase(); 90 | int findCode(String patternPrefix) { 91 | int index = message.indexOf(patternPrefix); 92 | if (index != -1) { 93 | String code = message.substring(index + patternPrefix.length); 94 | int endIndex = code.indexOf(")"); 95 | if (endIndex != -1) { 96 | try { 97 | int resultCode = int.parse(code.substring(0, endIndex)); 98 | if (resultCode != null) { 99 | return resultCode; 100 | } 101 | } catch (_) {} 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | int code = findCode("(sqlite code "); 108 | if (code != null) { 109 | return code; 110 | } 111 | code = findCode("(code "); 112 | if (code != null) { 113 | return code; 114 | } 115 | return null; 116 | } 117 | } 118 | 119 | Future wrapDatabaseException(Future action()) async { 120 | try { 121 | T result = await action(); 122 | return result; 123 | } on PlatformException catch (e) { 124 | if (e.code == sqliteErrorCode) { 125 | throw new SqfliteDatabaseException(e.message, e.details); 126 | //rethrow; 127 | } else { 128 | rethrow; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/src/sqflite_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | import 'dart:core'; 4 | 5 | import 'package:flutter/src/services/platform_channel.dart'; 6 | import 'package:sqflite/src/utils.dart'; 7 | 8 | import 'constant.dart' as constant; 9 | 10 | const String channelName = 'com.tekartik.sqflite'; 11 | 12 | Duration lockWarningDuration = constant.lockWarningDuration; 13 | void Function() lockWarningCallback = () { 14 | print('Warning database has been locked for ${lockWarningDuration}. ' 15 | 'Make sure you always use the transaction object for database operations during a transaction'); 16 | }; 17 | 18 | const MethodChannel channel = const MethodChannel(channelName); 19 | 20 | // Temp flag to test concurrent reads 21 | final bool supportsConcurrency = false; 22 | 23 | // Make it async safe for dart 2.0.0-dev28+ preview dart 2 24 | Future invokeMethod(String method, [dynamic arguments]) async { 25 | T result = await channel.invokeMethod(method, arguments); 26 | return result; 27 | } 28 | 29 | // Starting Dart preview 2, wrap the result 30 | class Rows extends PluginList> { 31 | Rows.from(List list) : super.from(list); 32 | 33 | @override 34 | Map operator [](int index) { 35 | Map item = rawList[index]; 36 | if (item is Map) { 37 | return item; 38 | } 39 | return item.cast(); 40 | } 41 | } 42 | 43 | Map newQueryResultSetMap( 44 | List columns, List> rows) { 45 | var map = {"columns": columns, "rows": rows}; 46 | return map; 47 | } 48 | 49 | QueryResultSet queryResultSetFromMap(Map queryResultSetMap) { 50 | return new QueryResultSet( 51 | queryResultSetMap["columns"] as List, queryResultSetMap["rows"] as List); 52 | } 53 | 54 | List> queryResultToList(dynamic queryResult) { 55 | // New 0.7.1 format 56 | // devPrint("queryResultToList: $queryResult"); 57 | if (queryResult == null) { 58 | return null; 59 | } 60 | if (queryResult is Map) { 61 | return queryResultSetFromMap(queryResult); 62 | } 63 | // dart1 64 | if (queryResult is List>) { 65 | return queryResult; 66 | } 67 | // dart2 support <= 0.7.0 - this is a list 68 | // to remove once done on iOS and Android 69 | Rows rows = new Rows.from(queryResult as List); 70 | return rows; 71 | } 72 | 73 | class QueryResultSet extends ListBase> { 74 | List> _rows; 75 | List _columns; 76 | Map _columnIndexMap; 77 | 78 | QueryResultSet(List rawColumns, List rawRows) { 79 | _columns = rawColumns?.cast(); 80 | _rows = rawRows?.cast(); 81 | if (_columns != null) { 82 | _columnIndexMap = {}; 83 | 84 | for (int i = 0; i < _columns.length; i++) { 85 | _columnIndexMap[_columns[i]] = i; 86 | } 87 | } 88 | } 89 | 90 | @override 91 | int get length => _rows?.length ?? 0; 92 | 93 | @override 94 | Map operator [](int index) { 95 | return new QueryRow(this, _rows[index]); 96 | } 97 | 98 | @override 99 | void operator []=(int index, Map value) { 100 | throw new UnsupportedError("read-only"); 101 | } 102 | 103 | @override 104 | set length(int newLength) { 105 | throw new UnsupportedError("read-only"); 106 | } 107 | 108 | int columnIndex(String name) { 109 | return _columnIndexMap[name]; 110 | } 111 | } 112 | 113 | class QueryRow extends MapBase { 114 | final QueryResultSet queryResultSet; 115 | final List row; 116 | 117 | QueryRow(this.queryResultSet, this.row); 118 | 119 | @override 120 | dynamic operator [](Object key) { 121 | int columnIndex = queryResultSet.columnIndex(key as String); 122 | if (columnIndex != null) { 123 | return row[columnIndex]; 124 | } 125 | return null; 126 | } 127 | 128 | @override 129 | void operator []=(String key, dynamic value) { 130 | throw new UnsupportedError("read-only"); 131 | } 132 | 133 | @override 134 | void clear() { 135 | throw new UnsupportedError("read-only"); 136 | } 137 | 138 | @override 139 | Iterable get keys => queryResultSet._columns; 140 | 141 | @override 142 | dynamic remove(Object key) { 143 | throw new UnsupportedError("read-only"); 144 | } 145 | } 146 | 147 | class BatchResult { 148 | final dynamic result; 149 | 150 | BatchResult(this.result); 151 | } 152 | 153 | class BatchResults extends PluginList { 154 | BatchResults.from(List list) : super.from(list); 155 | 156 | @override 157 | dynamic operator [](int index) { 158 | dynamic result = _list[index]; 159 | 160 | // list or map, this is a result 161 | if (result is Map) { 162 | return queryResultToList(result); 163 | } else if (result is List) { 164 | return queryResultToList(result); 165 | } 166 | 167 | return result; 168 | } 169 | } 170 | 171 | abstract class PluginList extends ListBase { 172 | final List _list; 173 | 174 | PluginList.from(List list) : _list = list; 175 | 176 | List get rawList => _list; 177 | 178 | dynamic rawElementAt(int index) => _list[index]; 179 | 180 | @override 181 | int get length => _list.length; 182 | 183 | @override 184 | set length(int newLength) { 185 | throw new UnsupportedError("read-only"); 186 | } 187 | 188 | @override 189 | void operator []=(int index, T value) { 190 | throw new UnsupportedError("read-only"); 191 | } 192 | } 193 | 194 | void setLockWarningInfo({Duration duration, void callback()}) { 195 | lockWarningDuration = duration ?? lockWarningDuration; 196 | lockWarningCallback = callback ?? lockWarningCallback; 197 | } 198 | -------------------------------------------------------------------------------- /lib/src/transaction.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:sqflite/sqflite.dart'; 4 | import 'package:sqflite/src/batch.dart'; 5 | import 'package:sqflite/src/database.dart'; 6 | 7 | class SqfliteTransaction extends SqfliteDatabaseExecutor 8 | implements Transaction { 9 | final SqfliteDatabase database; 10 | 11 | SqfliteTransaction(this.database); 12 | 13 | @override 14 | SqfliteDatabase get db => database; 15 | 16 | bool successfull; 17 | 18 | @override 19 | SqfliteTransaction get txn => this; 20 | 21 | @override 22 | Future applyBatch(Batch batch, {bool noResult}) { 23 | if (batch is SqfliteDatabaseBatch) { 24 | SqfliteDatabaseBatch sqfliteDatabaseBatch = batch; 25 | if (sqfliteDatabaseBatch.database != database) { 26 | throw new ArgumentError("database different in batch and transaction"); 27 | } 28 | } 29 | 30 | return database.txnApplyBatch(txn, batch as SqfliteBatch, 31 | noResult: noResult); 32 | } 33 | 34 | @override 35 | Batch batch() => new SqfliteTransactionBatch(this); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | export 'dart:async'; 2 | 3 | int parseInt(Object object) { 4 | if (object is int) { 5 | return object; 6 | } else if (object is String) { 7 | try { 8 | return int.parse(object); 9 | } catch (_) {} 10 | } 11 | return null; 12 | } 13 | 14 | @deprecated 15 | void devPrint(Object object) { 16 | print(object); 17 | } 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sqflite 2 | author: Tekartik 3 | homepage: https://github.com/tekartik/sqflite 4 | description: Flutter plugin for SQLite, a self-contained, high-reliability, 5 | embedded, SQL database engine. 6 | version: 0.11.0+5 7 | 8 | environment: 9 | sdk: ">=2.0.0-dev.35 <3.0.0" 10 | 11 | flutter: 12 | plugin: 13 | androidPackage: com.tekartik.sqflite 14 | pluginClass: SqflitePlugin 15 | 16 | dependencies: 17 | flutter: 18 | sdk: flutter 19 | synchronized: '>=1.5.1 <3.0.0' 20 | path: '>=1.5.1 <3.0.0' 21 | 22 | dev_dependencies: 23 | flutter_test: 24 | sdk: flutter 25 | -------------------------------------------------------------------------------- /test/deprecated_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import 'src_database_test.dart'; 5 | 6 | void main() { 7 | group("deprecated", () { 8 | test("transaction", () async { 9 | var db = mockDatabaseFactory.newEmptyDatabase(); 10 | await db.execute("test"); 11 | await db.insert("test", {'test': 1}); 12 | await db.update("test", {'test': 1}); 13 | await db.delete("test"); 14 | await db.query("test"); 15 | 16 | await db.transaction((txn) async { 17 | await txn.execute("test"); 18 | await txn.insert("test", {'test': 1}); 19 | await txn.update("test", {'test': 1}); 20 | await txn.delete("test"); 21 | await txn.query("test"); 22 | }); 23 | 24 | Batch batch = db.batch(); 25 | batch.execute("test"); 26 | batch.insert("test", {'test': 1}); 27 | batch.update("test", {'test': 1}); 28 | batch.delete("test"); 29 | batch.query("test"); 30 | // ignore: deprecated_member_use 31 | await batch.apply(); 32 | }); 33 | 34 | test('wrong database', () async { 35 | var db2 = mockDatabaseFactory.newEmptyDatabase(); 36 | var db = await mockDatabaseFactory.openDatabase(null, '', 37 | options: new OpenDatabaseOptions()) as MockDatabase; 38 | 39 | var batch = db2.batch(); 40 | 41 | await db.transaction((txn) async { 42 | try { 43 | // ignore: deprecated_member_use 44 | await txn.applyBatch(batch); 45 | fail("should fail"); 46 | } on ArgumentError catch (_) {} 47 | }); 48 | await db.close(); 49 | expect( 50 | db.methods, ['openDatabase', 'execute', 'execute', 'closeDatabase']); 51 | expect(db.sqls, [null, 'BEGIN IMMEDIATE', 'COMMIT', null]); 52 | }); 53 | }); 54 | test('simple', () async { 55 | var db = await mockDatabaseFactory.openDatabase(null, 'password') as MockDatabase; 56 | 57 | var batch = db.batch(); 58 | batch.execute("test"); 59 | // ignore: deprecated_member_use 60 | await batch.apply(); 61 | // ignore: deprecated_member_use 62 | await batch.apply(); 63 | await db.close(); 64 | expect(db.methods, [ 65 | 'openDatabase', 66 | 'execute', 67 | 'batch', 68 | 'execute', 69 | 'execute', 70 | 'batch', 71 | 'execute', 72 | 'closeDatabase' 73 | ]); 74 | expect(db.sqls, [ 75 | null, 76 | 'BEGIN IMMEDIATE', 77 | 'test', 78 | 'COMMIT', 79 | 'BEGIN IMMEDIATE', 80 | 'test', 81 | 'COMMIT', 82 | null 83 | ]); 84 | }); 85 | 86 | test('in_transaction', () async { 87 | var db = await mockDatabaseFactory.openDatabase(null, 'password') as MockDatabase; 88 | 89 | var batch = db.batch(); 90 | 91 | await db.transaction((txn) async { 92 | batch.execute("test"); 93 | 94 | // ignore: deprecated_member_use 95 | await txn.applyBatch(batch); 96 | // ignore: deprecated_member_use 97 | await txn.applyBatch(batch); 98 | }); 99 | await db.close(); 100 | expect(db.methods, [ 101 | 'openDatabase', 102 | 'execute', 103 | 'batch', 104 | 'batch', 105 | 'execute', 106 | 'closeDatabase' 107 | ]); 108 | expect(db.sqls, [null, 'BEGIN IMMEDIATE', 'test', 'test', 'COMMIT', null]); 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /test/list_mixin_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | class MyList1 extends Object with ListMixin> { 6 | final List _list; 7 | 8 | MyList1.from(this._list); 9 | 10 | @override 11 | Map operator [](int index) { 12 | Map value = _list[index]; 13 | return value.cast(); 14 | } 15 | 16 | @override 17 | void operator []=(int index, Map value) { 18 | throw "read-only"; 19 | } 20 | 21 | @override 22 | set length(int newLength) { 23 | throw "read-only"; 24 | } 25 | 26 | @override 27 | int get length => _list.length; 28 | } 29 | 30 | class MyList2 extends ListBase> { 31 | final List _list; 32 | 33 | MyList2.from(this._list); 34 | 35 | @override 36 | Map operator [](int index) { 37 | Map value = _list[index]; 38 | return value.cast(); 39 | } 40 | 41 | @override 42 | void operator []=(int index, Map value) { 43 | throw "read-only"; 44 | } 45 | 46 | @override 47 | set length(int newLength) { 48 | throw "read-only"; 49 | } 50 | 51 | @override 52 | int get length => _list.length; 53 | } 54 | 55 | void main() { 56 | group("mixin", () { 57 | // This fails on beta 1, should work now 58 | test('ListMixin', () { 59 | var raw = [ 60 | {'col': 1} 61 | ]; 62 | var rows = new MyList1.from(raw); 63 | expect(rows, raw); 64 | }); 65 | 66 | test('ListBase', () { 67 | var raw = [ 68 | {'col': 1} 69 | ]; 70 | var rows = new MyList2.from(raw); 71 | expect(rows, raw); 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/sqflite_exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqflite/src/exception.dart'; 3 | 4 | void main() { 5 | group("sqflite_exception", () { 6 | test("isUniqueContraint", () async { 7 | // Android 8 | String msg = "UNIQUE constraint failed: Test.name (code 2067))"; 9 | var exception = new SqfliteDatabaseException(msg, null); 10 | expect(exception.isDatabaseClosedError(), isFalse); 11 | expect(exception.isReadOnlyError(), isFalse); 12 | expect(exception.isNoSuchTableError(), isFalse); 13 | expect(exception.isOpenFailedError(), isFalse); 14 | expect(exception.isSyntaxError(), isFalse); 15 | expect(exception.isUniqueConstraintError(), isTrue); 16 | expect(exception.isUniqueConstraintError("Test.name"), isTrue); 17 | 18 | msg = "UNIQUE constraint failed: Test.name (code 1555))"; 19 | expect(exception.isSyntaxError(), isFalse); 20 | expect(exception.isUniqueConstraintError(), isTrue); 21 | expect(exception.isUniqueConstraintError("Test.name"), isTrue); 22 | }); 23 | 24 | test("isSyntaxError", () async { 25 | // Android 26 | String msg = 'near "DUMMY": syntax error (code 1)'; 27 | var exception = new SqfliteDatabaseException(msg, null); 28 | expect(exception.isDatabaseClosedError(), isFalse); 29 | expect(exception.isReadOnlyError(), isFalse); 30 | expect(exception.isNoSuchTableError(), isFalse); 31 | expect(exception.isOpenFailedError(), isFalse); 32 | expect(exception.isSyntaxError(), isTrue); 33 | expect(exception.isUniqueConstraintError(), isFalse); 34 | expect(exception.getResultCode(), 1); 35 | }); 36 | 37 | test("isNoSuchTable", () async { 38 | // Android 39 | String msg = "no such table: Test (code 1)"; 40 | var exception = new SqfliteDatabaseException(msg, null); 41 | expect(exception.isDatabaseClosedError(), isFalse); 42 | expect(exception.isReadOnlyError(), isFalse); 43 | expect(exception.isNoSuchTableError(), isTrue); 44 | expect(exception.isNoSuchTableError("Test"), isTrue); 45 | expect(exception.isNoSuchTableError("Other"), isFalse); 46 | expect(exception.isOpenFailedError(), isFalse); 47 | expect(exception.isSyntaxError(), isFalse); 48 | expect(exception.isUniqueConstraintError(), isFalse); 49 | expect(exception.getResultCode(), 1); 50 | }); 51 | 52 | test("getResultCode", () async { 53 | // Android 54 | String msg = "UNIQUE constraint failed: Test.name (code 2067))"; 55 | var exception = new SqfliteDatabaseException(msg, null); 56 | expect(exception.getResultCode(), 2067); 57 | exception = new SqfliteDatabaseException( 58 | "UNIQUE constraint failed: Test.name (code 1555))", null); 59 | expect(exception.getResultCode(), 1555); 60 | exception = new SqfliteDatabaseException( 61 | 'near "DUMMY": syntax error (code 1)', null); 62 | expect(exception.getResultCode(), 1); 63 | 64 | exception = new SqfliteDatabaseException( 65 | 'attempt to write a readonly database (code 8)) running Open read-only', 66 | null); 67 | expect(exception.getResultCode(), 8); 68 | }); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /test/sqflite_impl_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:sqflite/src/sqflite_impl.dart'; 4 | 5 | void main() { 6 | group("sqflite", () { 7 | const MethodChannel channel = const MethodChannel('com.tekartik.sqflite'); 8 | 9 | final List log = []; 10 | String response; 11 | 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | log.add(methodCall); 14 | return response; 15 | }); 16 | 17 | tearDown(() { 18 | log.clear(); 19 | }); 20 | 21 | test("supportsConcurrency", () async { 22 | expect(supportsConcurrency, isFalse); 23 | }); 24 | 25 | test('Rows', () { 26 | var raw = [ 27 | {'col': 1} 28 | ]; 29 | var rows = new Rows.from(raw); 30 | var row = rows.first; 31 | expect(rows, raw); 32 | expect(row, {"col": 1}); 33 | }); 34 | 35 | test('ResultSet', () { 36 | var raw = { 37 | "columns": ["column"], 38 | "rows": [ 39 | [1] 40 | ] 41 | }; 42 | var queryResultSet = new QueryResultSet([ 43 | "column" 44 | ], [ 45 | [1] 46 | ]); 47 | expect(queryResultSet.columnIndex("dummy"), isNull); 48 | expect(queryResultSet.columnIndex("column"), 0); 49 | var row = queryResultSet.first; 50 | //expect(rows, raw); 51 | expect(row, {"column": 1}); 52 | 53 | var queryResultSetMap = { 54 | "columns": ["id", "name"], 55 | "rows": [ 56 | [1, "item 1"], 57 | [2, "item 2"] 58 | ] 59 | }; 60 | var expected = [ 61 | {'id': 1, 'name': 'item 1'}, 62 | {'id': 2, 'name': 'item 2'} 63 | ]; 64 | expect(queryResultToList(queryResultSetMap), expected); 65 | expect(queryResultToList(expected), expected); 66 | expect(queryResultToList(raw), [ 67 | {'column': 1} 68 | ]); 69 | 70 | expect(queryResultToList({}), []); 71 | }); 72 | 73 | test('lockWarning', () {}); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /test/sqflite_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | void main() { 6 | group("sqflite", () { 7 | const MethodChannel channel = const MethodChannel('com.tekartik.sqflite'); 8 | 9 | final List log = []; 10 | String response; 11 | 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | log.add(methodCall); 14 | return response; 15 | }); 16 | 17 | tearDown(() { 18 | log.clear(); 19 | }); 20 | 21 | test("setDebugModeOn", () async { 22 | await Sqflite.setDebugModeOn(); 23 | expect(log.first.method, "debugMode"); 24 | expect(log.first.arguments, true); 25 | }); 26 | 27 | // Check that public api are exported 28 | test("exported", () { 29 | Database db; 30 | 31 | db?.batch(); 32 | db?.update(null, null); 33 | db?.transaction((Transaction txt) => null); 34 | 35 | Transaction transaction; 36 | transaction?.execute(null, null); 37 | 38 | expect(ConflictAlgorithm.abort, isNotNull); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /test/sql_builder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | //import 'package:test/test.dart'; 3 | import 'package:sqflite/src/sql_builder.dart'; 4 | 5 | void main() { 6 | group("sql_builder", () { 7 | test("delete", () { 8 | SqlBuilder builder = new SqlBuilder.delete("test", 9 | where: "value = ?", whereArgs: [1]); 10 | expect(builder.sql, "DELETE FROM test WHERE value = ?"); 11 | expect(builder.arguments, [1]); 12 | 13 | builder = new SqlBuilder.delete("test"); 14 | expect(builder.sql, "DELETE FROM test"); 15 | expect(builder.arguments, isNull); 16 | }); 17 | 18 | test("query", () { 19 | SqlBuilder builder = new SqlBuilder.query("test"); 20 | expect(builder.sql, "SELECT * FROM test"); 21 | expect(builder.arguments, isNull); 22 | 23 | builder = new SqlBuilder.query("test", 24 | distinct: true, 25 | columns: ["value"], 26 | where: "value = ?", 27 | whereArgs: [1], 28 | groupBy: "group_value", 29 | having: "value > 0", 30 | orderBy: "other_value", 31 | limit: 2, 32 | offset: 3); 33 | expect(builder.sql, 34 | "SELECT DISTINCT value FROM test WHERE value = ? GROUP BY group_value HAVING value > 0 ORDER BY other_value LIMIT 2 OFFSET 3"); 35 | expect(builder.arguments, [1]); 36 | }); 37 | 38 | test("insert", () { 39 | try { 40 | new SqlBuilder.insert("test", null); 41 | fail('should fail, no nullColumnHack'); 42 | } on ArgumentError catch (_) {} 43 | 44 | SqlBuilder builder = 45 | new SqlBuilder.insert("test", null, nullColumnHack: "value"); 46 | expect(builder.sql, "INSERT INTO test (value) VALUES (NULL)"); 47 | expect(builder.arguments, isNull); 48 | 49 | builder = new SqlBuilder.insert("test", {"value": 1}); 50 | expect(builder.sql, "INSERT INTO test (value) VALUES (?)"); 51 | expect(builder.arguments, [1]); 52 | 53 | builder = new SqlBuilder.insert( 54 | "test", {"value": 1, "other_value": null}); 55 | expect(builder.sql, 56 | "INSERT INTO test (value, other_value) VALUES (?, NULL)"); 57 | expect(builder.arguments, [1]); 58 | }); 59 | 60 | test("update", () { 61 | try { 62 | new SqlBuilder.update("test", null); 63 | fail('should fail, no values'); 64 | } on ArgumentError catch (_) {} 65 | 66 | SqlBuilder builder = 67 | new SqlBuilder.update("test", {"value": 1}); 68 | expect(builder.sql, "UPDATE test SET value = ?"); 69 | expect(builder.arguments, [1]); 70 | 71 | builder = new SqlBuilder.update( 72 | "test", {"value": 1, "other_value": null}); 73 | expect(builder.sql, "UPDATE test SET value = ?, other_value = NULL"); 74 | expect(builder.arguments, [1]); 75 | 76 | // testing where 77 | builder = new SqlBuilder.update('test', {"value": 1}, 78 | where: "a = ? AND b = ?", whereArgs: ['some_test', 1]); 79 | expect(builder.arguments, [1, "some_test", 1]); 80 | }); 81 | 82 | test("query", () { 83 | SqlBuilder builder = new SqlBuilder.query("table", orderBy: "value"); 84 | expect(builder.sql, 'SELECT * FROM "table" ORDER BY value'); 85 | expect(builder.arguments, isNull); 86 | 87 | builder = 88 | new SqlBuilder.query("table", orderBy: "column_1 ASC, column_2 DESC"); 89 | expect(builder.sql, 90 | 'SELECT * FROM "table" ORDER BY column_1 ASC, column_2 DESC'); 91 | expect(builder.arguments, isNull); 92 | 93 | // testing where 94 | builder = new SqlBuilder.query('test', 95 | where: "a = ? AND b = ?", whereArgs: ['some_test', 1]); 96 | expect(builder.arguments, ["some_test", 1]); 97 | }); 98 | 99 | test("isEscapedName", () { 100 | expect(isEscapedName(null), false); 101 | expect(isEscapedName("group"), false); 102 | expect(isEscapedName("'group'"), false); 103 | expect(isEscapedName('"group"'), true); 104 | expect(isEscapedName("`group`"), true); 105 | expect(isEscapedName("`group'"), false); 106 | expect(isEscapedName("\"group\""), true); 107 | }); 108 | 109 | test("escapeName", () { 110 | expect(escapeName(null), null); 111 | expect(escapeName("group"), '"group"'); 112 | expect(escapeName("dummy"), "dummy"); 113 | 114 | for (String name in escapeNames) { 115 | expect(escapeName(name), '"${name}"'); 116 | } 117 | }); 118 | 119 | test("unescapeName", () { 120 | expect(unescapeName(null), null); 121 | 122 | expect(unescapeName("dummy"), "dummy"); 123 | expect(unescapeName("'dummy'"), "'dummy'"); 124 | expect(unescapeName("'group'"), "'group'"); 125 | expect(unescapeName('"group"'), "group"); 126 | expect(unescapeName('`group`'), "group"); 127 | 128 | for (String name in escapeNames) { 129 | expect(unescapeName('"$name"'), name); 130 | } 131 | }); 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /test/sql_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:sqflite/sql.dart'; 4 | 5 | void main() { 6 | group("sqflite", () { 7 | const MethodChannel channel = const MethodChannel('com.tekartik.sqflite'); 8 | 9 | final List log = []; 10 | String response; 11 | 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | log.add(methodCall); 14 | return response; 15 | }); 16 | 17 | tearDown(() { 18 | log.clear(); 19 | }); 20 | 21 | test("exported", () { 22 | expect(ConflictAlgorithm.abort, isNotNull); 23 | }); 24 | 25 | test("escapeName_export", () { 26 | expect(escapeName("group"), '"group"'); 27 | }); 28 | 29 | test("unescapeName_export", () { 30 | expect(unescapeName('"group"'), "group"); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /tool/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fast fail the script on failures. 4 | set -e 5 | 6 | # not working currently 7 | # $FLUTTER_ROOT/bin/cache/dart-sdk/bin/dartdoc 8 | flutter analyze lib test 9 | flutter test 10 | 11 | pushd example 12 | flutter analyze lib test 13 | flutter test -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Fast fail the script on failures. 4 | # and print line as they are read 5 | set -ev 6 | 7 | flutter --version 8 | 9 | flutter packages get 10 | 11 | flutter analyze --no-current-package lib test 12 | # flutter analyze --no-current-package --preview-dart-2 lib test 13 | 14 | flutter test 15 | # flutter test --preview-dart-2 16 | 17 | # example 18 | pushd example 19 | 20 | flutter packages get 21 | 22 | flutter analyze --no-current-package lib test 23 | # flutter analyze --no-current-package --preview-dart-2 lib test 24 | 25 | flutter test 26 | # flutter test --preview-dart-2 27 | 28 | # dartdoc --------------------------------------------------------------------------------