├── .gitignore ├── .metadata ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── sqlcool │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── 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 │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── docs ├── Makefile ├── batch_ops.rst ├── bloc.rst ├── changefeed.rst ├── conf.py ├── db_ops.rst ├── dbmodels │ ├── foreign_keys.rst │ ├── init.rst │ ├── mutate.rst │ └── select.rst ├── index.rst ├── init.rst ├── make.bat └── schema.rst ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── 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 │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── 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 │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── appbar.dart │ ├── conf.dart │ ├── database.dart │ ├── dbmodels │ │ ├── db.dart │ │ ├── dbmodels.dart │ │ ├── models │ │ │ ├── car.dart │ │ │ └── manufacturer.dart │ │ └── schema.dart │ ├── dbviewer │ │ ├── dbviewer.dart │ │ └── table.dart │ ├── dialogs.dart │ ├── init_db.dart │ ├── main.dart │ └── pages │ │ ├── index.dart │ │ ├── join.dart │ │ ├── select_bloc.dart │ │ └── upsert.dart └── pubspec.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── 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 │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── sqlcool.dart └── src │ ├── bloc_select.dart │ ├── bloc_select_rows.dart │ ├── database.dart │ ├── exceptions.dart │ ├── models.dart │ ├── safe_api.dart │ └── schema │ └── models │ ├── column.dart │ ├── dbmodels.dart │ ├── row.dart │ ├── schema.dart │ └── table.dart ├── pubspec.yaml └── test ├── base.dart ├── changefeed_test.dart ├── db_models_test.dart ├── db_test.dart ├── dbmodels ├── car.dart ├── conf.dart ├── manufacturer.dart └── schema.dart ├── errors_test.dart └── schema_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Sphinx documentation 22 | docs/_build/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | pubspec.lock 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Android Studio will place build artifacts here 46 | /android/app/debug 47 | /android/app/profile 48 | /android/app/release 49 | 50 | # Coverage report 51 | coverage/output/ 52 | coverage/lcov.info -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | sudo: false 4 | addons: 5 | apt: 6 | sources: 7 | - ubuntu-toolchain-r-test 8 | packages: 9 | - libstdc++6 10 | before_script: 11 | - git clone https://github.com/flutter/flutter.git -b stable --depth 1 12 | - ./flutter/bin/flutter doctor 13 | - gem install coveralls-lcov 14 | script: 15 | - ./flutter/bin/flutter test --coverage 16 | after_success: 17 | - coveralls-lcov coverage/lcov.info 18 | cache: 19 | directories: 20 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 5.1.1 4 | 5 | Fix insert method 6 | 7 | # 5.1.0 8 | 9 | Better null values management. Dart `null` values as now stored in the 10 | database as proper `NULL` values. This could break null checks in data 11 | saved with previous versions 12 | 13 | # 5.0.2 14 | 15 | - Save null values to database as NULL in Db models 16 | 17 | # 5.0.1 18 | 19 | - Fix null values in Db models 20 | 21 | # 5.0.0 22 | 23 | - Update Flutter project and dependencies 24 | 25 | # 4.3.1 26 | 27 | - Refactor and fix `DbModel.sqlJoin` 28 | - Add a toString method to `DbColumn` 29 | 30 | # 4.3.0 31 | 32 | - Update dependencies 33 | - Fix `boolean` column in schema 34 | - Fix `unique` in schema 35 | - Fix `uniqueTogether` in schema 36 | - Fix edge case in `DbModel.sqlJoin` 37 | - Deprecate `insertIfNotExists` 38 | - Deprecate `DbModels.insertIfNotExists` 39 | 40 | # 4.2.0 41 | 42 | - Update dependencies 43 | - Add `insertManageConflict` method 44 | - Fix typo in `confligAlgoritm` parameter for `batchInsert` 45 | 46 | ## 4.1.1 47 | 48 | Fix update query constructor bug #16 49 | 50 | ## 4.1.0 51 | 52 | - Use extra_pedantic for stronger analysis_options 53 | - Add more custom exceptions 54 | - Add a `DbModel.sqlInsertIfNotExists` method 55 | - Add a `preserveColumn` parameter to `DbModel.sqlUpsert` 56 | 57 | ## 4.0.0 58 | 59 | - Add informative getters to the schema 60 | - Join on multiple foreign keys 61 | - Database models 62 | - Query support in `SelectBloc` 63 | - Update dependencies 64 | - Use more strict analysis options 65 | 66 | ## 3.2.1 67 | 68 | - Run create queries and schema for asset database 69 | - Use create if not exists in create table query 70 | 71 | ## 3.2.0 72 | 73 | - Update to Dart sdk 2.2.2 74 | - Update dependencies 75 | 76 | ## 3.1.1 77 | 78 | - Use pedantic for static analysis 79 | - Add more tests 80 | - Improve the docs 81 | - Linting 82 | 83 | ## 3.1.0 84 | 85 | - Add a `timestamp` column type to schema 86 | - Add a `data` property to `DatabaseChangeType` 87 | - Fix the `upsert` method to be testable 88 | - Add more tests 89 | 90 | ## 3.0.0 91 | 92 | **Breaking change**: the `SynchronizedMap` feature was removed due to broken dependencies after the Dart Sdk 2.4.0 upgrade 93 | 94 | ## 2.9.0 95 | 96 | - Fix index in `DbTable` in case of same row name for different tables 97 | - Fix the initialization when the `fromAsset` parameter is used 98 | - Fix schema constructor in case of multiple foreign keys 99 | - Add the `timestamp` method to `DbTable` 100 | - Add a `uniqueTogether` method to `DbTable` 101 | - Add a blob method to schema constructor 102 | - Improve the docs for schema definition 103 | 104 | ## 2.8.2 105 | 106 | - Add the columns getter for `DbSchema` 107 | - Fix `defaultValue` for the`real` method of `DbSchema` 108 | - Fix the example 109 | 110 | ## 2.8.1 111 | 112 | - Update dependencies 113 | - Improve schema management 114 | - Minor fix in `SynchronizedMap` 115 | - Add the `hasSchema` getter 116 | 117 | ## 2.8.0 118 | 119 | - Add the `batchInsert` method 120 | - Add the `schema` parameter to `init` 121 | - Improve the `count` method 122 | - Update the changefeed from batchInsert 123 | - Fix nullables in schema constructor 124 | - Improve `foreignKey` in schema constructor 125 | 126 | ## 2.7.0 127 | 128 | - Add the database schema constructor 129 | 130 | ## 2.6.1 131 | 132 | - Add the `columns` parameter to `SychronizedMap` 133 | 134 | ## 2.6.0 135 | 136 | - Add the synchronized map feature 137 | 138 | ## 2.5.0 139 | 140 | - Add the group by sql clause to select and join methods 141 | - Add the upsert method 142 | - Use transactions for all queries 143 | - Remove the default values for offset and limit in join query 144 | 145 | ## 2.4.0 146 | 147 | - Add the ability to use an existing Sqflite database 148 | - Make all the DatabaseChangeEvent parameters final 149 | - Add a table parameter to DatabaseChangeEvent 150 | - Update SelectBloc to use the table parameter of DatabaseChangeEvent 151 | - Use travis-ci builds 152 | - Start adding tests 153 | 154 | ## 2.3.0 155 | 156 | - Update dependencies 157 | - Add the `update` method to `SelectBloc` 158 | 159 | ## 2.2.0 160 | 161 | - Add the `absolutePath` parameter to the `init` method 162 | - Use more strict linting rules 163 | - Improve docstrings 164 | 165 | ## 2.1.1 166 | 167 | - Fix race condition in SelectBloc 168 | - Fix in the `fromAsset` option of `init`: create the directories path if needed instead of throwing an error 169 | 170 | ## 2.1.0 171 | 172 | - Add the onReady callback 173 | - Upgrade dependencies 174 | 175 | ## 2.0.0 176 | 177 | **Breaking changes**: 178 | 179 | - The default `Db` instance has been removed 180 | - The `database` parameter is now required for `SelectBloc` 181 | - The `changeType` parameter in `changefeed` has been renamed `type` and now uses the `DatabaseChange` data type 182 | 183 | New features: 184 | 185 | - Add support for the Sqflite debug mode 186 | - Add a query timer 187 | - Add the `query` method 188 | 189 | Changes and fixes: 190 | 191 | - Add a check to make sure the database is ready before running any query 192 | - Better examples 193 | - Various minor fixes 194 | 195 | 196 | ## 1.2.0 197 | 198 | - Downgrade to path_provider 0.4.1 199 | - Add mutexes for write operations 200 | - Add the query to the changefeed info 201 | 202 | - Fix return values for update and delete 203 | - Fix bloc select verbose param 204 | - Fix verbosity for update and insert queries 205 | - Improve the example 206 | - Improve the doc and readme 207 | 208 | ## 1.1.2 209 | 210 | Fix: close `_changeFeedController` sink 211 | 212 | ## 1.1.1 213 | 214 | Minor fixes 215 | 216 | ## 1.1.0 217 | 218 | Add changefeed and reactive select bloc 219 | 220 | ## 1.0.0 221 | 222 | Initial release 223 | 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:extra_pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | strong-mode: 5 | implicit-casts: false 6 | implicit-dynamic: false 7 | errors: 8 | missing_return: error 9 | missing_required_param: error 10 | invalid_use_of_protected_member: error 11 | dead_code: info 12 | sdk_version_async_exported_from_core: ignore 13 | linter: 14 | rules: 15 | - unnecessary_statements 16 | - unnecessary_lambdas 17 | - avoid_classes_with_only_static_members 18 | - avoid_renaming_method_parameters 19 | - camel_case_types 20 | - constant_identifier_names 21 | - cascade_invocations 22 | - omit_local_variable_types 23 | - public_member_api_docs 24 | #- avoid_bool_literals_in_conditional_expressions 25 | #- avoid_positional_boolean_parameters -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.sqlcool" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/sqlcool/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.sqlcool 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Sqlcool 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/batch_ops.rst: -------------------------------------------------------------------------------- 1 | Batch insert 2 | ============ 3 | 4 | .. highlight:: dart 5 | 6 | :: 7 | 8 | import 'package:sqflite/sqlflite.dart'; 9 | import 'package:sqlcool/sqlcool.dart'; 10 | 11 | var rows = >[{"name": "one"}, {"name": "two"}]; 12 | 13 | await db.batchInsert( 14 | table: "item", 15 | rows: rows, 16 | confligAlgoritm: ConflictAlgorithm.replace) -------------------------------------------------------------------------------- /docs/bloc.rst: -------------------------------------------------------------------------------- 1 | Using the bloc pattern for select 2 | ================================= 3 | 4 | A `SelectBloc` is available to use the bloc pattern. 5 | 6 | Select bloc 7 | ----------- 8 | 9 | .. highlight:: dart 10 | 11 | :: 12 | 13 | import 'package:flutter/material.dart'; 14 | import 'package:sqlcool/sqlcool.dart'; 15 | 16 | class _PageSelectBlocState extends State { 17 | SelectBloc bloc; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | this.bloc = SelectBloc( 23 | table: "items", orderBy: "name", verbose: true); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: Text("My app"), 31 | ), 32 | body: StreamBuilder>( 33 | stream: bloc.items, 34 | builder: (BuildContext context, AsyncSnapshot snapshot) { 35 | if (snapshot.hasData) { 36 | // the select query has not found anything 37 | if (snapshot.data.length == 0) { 38 | return Center( 39 | child: Text( 40 | "No data. Use the + in the appbar to insert an item"), 41 | ); 42 | } 43 | // the select query has results 44 | return ListView.builder( 45 | itemCount: snapshot.data.length, 46 | itemBuilder: (BuildContext context, int index) { 47 | var item = snapshot.data[index]; 48 | return ListTile( 49 | title: GestureDetector( 50 | child: Text(item["name"]), 51 | onTap: () => print("Action"), 52 | ), 53 | ); 54 | }); 55 | } else { 56 | // the select query is still running 57 | return CircularProgressIndicator(); 58 | } 59 | }), 60 | ); 61 | } 62 | } 63 | 64 | class PageSelectBloc extends StatefulWidget { 65 | @override 66 | _PageSelectBlocState createState() => _PageSelectBlocState(); 67 | } 68 | 69 | ``SelectBloc`` class: 70 | 71 | Required parameter: 72 | 73 | :table: *String* name of the table, required 74 | 75 | Optional parameters: 76 | 77 | :select: *String* the select sql clause 78 | :where: *String* the where sql clause 79 | :joinTable: *String* join table name 80 | :joinOn: *String* join on sql clause 81 | :orderBy: *String* the sql order_by clause 82 | :limit: *int* the sql limit clause 83 | :offset: *int* the sql offset clause 84 | :reactive: *bool* if `true` the select bloc will react to database changes. Defaults to `false` 85 | :verbose: *bool* ``true`` or ``false`` 86 | :database: *Db* the database to use: default is the default database 87 | 88 | Join queries 89 | ------------ 90 | 91 | :: 92 | 93 | @override 94 | void initState() { 95 | super.initState(); 96 | this.bloc = SelectBloc(table: "product", offset: 10, limit: 20, 97 | select: "id, name, price, category.name as category_name", 98 | joinTable: "category", 99 | joinOn: "product.category=category.id"); 100 | } 101 | -------------------------------------------------------------------------------- /docs/changefeed.rst: -------------------------------------------------------------------------------- 1 | Reactivity 2 | ========== 3 | 4 | Changefeed 5 | ---------- 6 | 7 | A changefeed is available (inspired by `Rethinkdb 8 | `_). 9 | It's a stream that will notify about any change in 10 | the database. 11 | 12 | .. highlight:: dart 13 | 14 | :: 15 | 16 | import 'dart:async'; 17 | import 'package:flutter/material.dart'; 18 | import 'package:sqlcool/sqlcool.dart'; 19 | import 'dialogs.dart'; 20 | 21 | class _PageState extends State { 22 | StreamSubscription _changefeed; 23 | 24 | @override 25 | void initState() { 26 | _changefeed = db.changefeed.listen((change) { 27 | print("CHANGE IN THE DATABASE:"); 28 | print("Change type: ${change.type}"); 29 | print("Number of items impacted: ${change.value}"); 30 | print("Query: ${change.query}"); 31 | if (change.type == DatabaseChange.update) { 32 | print("${change.value} items updated"); 33 | } 34 | }); 35 | super.initState(); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | _changefeed.cancel(); 41 | super.dispose(); 42 | } 43 | 44 | // ... 45 | } 46 | 47 | class Page extends StatefulWidget { 48 | @override 49 | _PageState createState() => _PageState(); 50 | } 51 | 52 | 53 | Reactive select bloc 54 | -------------------- 55 | 56 | A ``SelectBloc`` can take a ``reactive`` parameter. If it is ``true`` the bloc 57 | will automatically rebuild itself on any database change 58 | 59 | Check the `example 60 | `_ for usage demo. -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'Sqlcool' 23 | copyright = '2019, synw' 24 | author = 'synw' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '0.1' 30 | 31 | 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = None 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path . 68 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 69 | 70 | # The name of the Pygments (syntax highlighting) style to use. 71 | pygments_style = 'sphinx' 72 | 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | import sphinx_rtd_theme 80 | 81 | html_theme = "sphinx_rtd_theme" 82 | 83 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 84 | 85 | # Theme options are theme-specific and customize the look and feel of a theme 86 | # further. For a list of options available for each theme, see the 87 | # documentation. 88 | # 89 | # html_theme_options = {} 90 | 91 | # Add any paths that contain custom static files (such as style sheets) here, 92 | # relative to this directory. They are copied after the builtin static files, 93 | # so a file named "default.css" will overwrite the builtin "default.css". 94 | html_static_path = ['_static'] 95 | 96 | # Custom sidebar templates, must be a dictionary that maps document names 97 | # to template names. 98 | # 99 | # The default sidebars (for documents that don't match any pattern) are 100 | # defined by theme itself. Builtin themes are using these templates by 101 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 102 | # 'searchbox.html']``. 103 | # 104 | # html_sidebars = {} 105 | 106 | 107 | # -- Options for HTMLHelp output --------------------------------------------- 108 | 109 | # Output file base name for HTML help builder. 110 | htmlhelp_basename = 'Sqlcooldoc' 111 | 112 | 113 | # -- Options for LaTeX output ------------------------------------------------ 114 | 115 | latex_elements = { 116 | # The paper size ('letterpaper' or 'a4paper'). 117 | # 118 | # 'papersize': 'letterpaper', 119 | 120 | # The font size ('10pt', '11pt' or '12pt'). 121 | # 122 | # 'pointsize': '10pt', 123 | 124 | # Additional stuff for the LaTeX preamble. 125 | # 126 | # 'preamble': '', 127 | 128 | # Latex figure (float) alignment 129 | # 130 | # 'figure_align': 'htbp', 131 | } 132 | 133 | # Grouping the document tree into LaTeX files. List of tuples 134 | # (source start file, target name, title, 135 | # author, documentclass [howto, manual, or own class]). 136 | latex_documents = [ 137 | (master_doc, 'Sqlcool.tex', 'Sqlcool Documentation', 138 | 'synw', 'manual'), 139 | ] 140 | 141 | 142 | # -- Options for manual page output ------------------------------------------ 143 | 144 | # One entry per manual page. List of tuples 145 | # (source start file, name, description, authors, manual section). 146 | man_pages = [ 147 | (master_doc, 'sqlcool', 'Sqlcool Documentation', 148 | [author], 1) 149 | ] 150 | 151 | 152 | # -- Options for Texinfo output ---------------------------------------------- 153 | 154 | # Grouping the document tree into Texinfo files. List of tuples 155 | # (source start file, target name, title, author, 156 | # dir menu entry, description, category) 157 | texinfo_documents = [ 158 | (master_doc, 'Sqlcool', 'Sqlcool Documentation', 159 | author, 'Sqlcool', 'One line description of project.', 160 | 'Miscellaneous'), 161 | ] -------------------------------------------------------------------------------- /docs/db_ops.rst: -------------------------------------------------------------------------------- 1 | Database operations 2 | =================== 3 | 4 | Insert 5 | ------ 6 | 7 | .. highlight:: dart 8 | 9 | :: 10 | 11 | import 'package:sqlcool/sqlcool.dart'; 12 | 13 | Map row = { 14 | slug: "my-item", 15 | name: "My item", 16 | } 17 | await db.insert(table: "category", row: row, verbose: true); 18 | 19 | Required parameters: 20 | 21 | :table: *String* name of the table, required 22 | :row: *Map* data, required 23 | 24 | Optional parameter: 25 | 26 | :verbose: *bool* ``true`` or ``false`` 27 | 28 | Select 29 | ------ 30 | 31 | :: 32 | 33 | import 'package:sqlcool/sqlcool.dart'; 34 | 35 | List> rows = 36 | await db.select(table: "product", limit: 20, where: "name LIKE '%something%'", 37 | orderBy: "price ASC"); 38 | 39 | Required parameter: 40 | 41 | :table: *String* name of the table, required 42 | 43 | Optional parameters: 44 | 45 | :columns: *String* the columns to select: default is `"*"` 46 | :where: *String* the where sql clause 47 | :orderBy: *String* the sql order by clause 48 | :groupBy: *String* the sql group by clause 49 | :limit: *int* the sql limit clause 50 | :offset: *int* the sql offset clause 51 | :verbose: *bool* ``true`` or ``false`` 52 | 53 | Update 54 | ------ 55 | 56 | :: 57 | 58 | import 'package:sqlcool/sqlcool.dart'; 59 | 60 | Map row = { 61 | slug: "my-item-new", 62 | name: "My item new", 63 | } 64 | int updated = await db.update(table: "category", row: row, where: "id=1", verbose: true); 65 | 66 | Required parameters: 67 | 68 | :table: *String* name of the table, required 69 | :row: *Map* data, required 70 | 71 | Optional parameters: 72 | 73 | :where: *String* the where sql clause 74 | :verbose: *bool* ``true`` or ``false`` 75 | 76 | 77 | Delete 78 | ------ 79 | 80 | :: 81 | 82 | import 'package:sqlcool/sqlcool.dart'; 83 | 84 | await db.delete(table: "category", where: "id=1"); 85 | 86 | Required parameters: 87 | 88 | :table: *String* name of the table, required 89 | :where: *String* the where sql clause 90 | 91 | Optional parameter: 92 | 93 | :verbose: *bool* ``true`` or ``false`` 94 | 95 | Upsert 96 | ------ 97 | 98 | .. highlight:: dart 99 | 100 | :: 101 | 102 | import 'package:sqlcool/sqlcool.dart'; 103 | 104 | Map row = { 105 | slug: "my-item", 106 | name: "My item", 107 | } 108 | await db.upsert( 109 | table: "product", 110 | row: row, 111 | preserveRow: "category", 112 | indexColumn: "id" 113 | ); 114 | 115 | Required parameters: 116 | 117 | :table: *String* name of the table, required 118 | :row: *Map* data, required 119 | 120 | Optionnal parameters: 121 | 122 | :preserveColumns: *List* a list of columns to preserve, 123 | the data in these columns will not be updated. Note: the `indexColumn` 124 | parameter is required 125 | when using this method (used to retrieve the existing data). 126 | :indexColumn: *String* the reference index column use to retrieve 127 | existing data in case of preserve 128 | :verbose: *bool* ``true`` or ``false`` 129 | 130 | Join 131 | ---- 132 | 133 | :: 134 | 135 | import 'package:sqlcool/sqlcool.dart'; 136 | 137 | List> rows = await db.join( 138 | table: "product", offset: 10, limit: 20, 139 | columns: "id, name, price, category.name as category_name", 140 | joinTable: "category", 141 | joinOn: "product.category=category.id"); 142 | 143 | 144 | Required parameter: 145 | 146 | :table: *String* name of the table, required 147 | 148 | Optional parameters: 149 | 150 | :columns: *String* the select sql clause 151 | :where: *String* the where sql clause 152 | :joinTable: *String* join table name 153 | :joinOn: *String* join on sql clause 154 | :orderBy: *String* the sql order by clause 155 | :groupBy: *String* the sql group by clause 156 | :limit: *int* the sql limit clause 157 | :offset: *int* the sql offset clause 158 | :verbose: *bool* ``true`` or ``false`` 159 | 160 | Exists 161 | ------ 162 | 163 | :: 164 | 165 | import 'package:sqlcool/sqlcool.dart'; 166 | 167 | bool exists = await db.exists(table: "category", "id=3"); 168 | 169 | Required parameters: 170 | 171 | :table: *String* name of the table, required 172 | :where: *String* the where sql clause 173 | 174 | Raw query 175 | --------- 176 | 177 | :: 178 | 179 | import 'package:sqlcool/sqlcool.dart'; 180 | 181 | List> result = await db.query("SELECT * FROM mytable"); 182 | 183 | Required parameters: 184 | 185 | :query: *String* the sql query, required 186 | :verbose: *bool* ``true`` or ``false`` 187 | -------------------------------------------------------------------------------- /docs/dbmodels/foreign_keys.rst: -------------------------------------------------------------------------------- 1 | Foreign keys support 2 | ==================== 3 | 4 | The database models support foreign keys. Example: create a foreign key model: 5 | 6 | .. highlight:: dart 7 | 8 | :: 9 | 10 | class Manufacturer with DbModel { 11 | Manufacturer({this.name}); 12 | 13 | final String name; 14 | 15 | @override 16 | int id; 17 | 18 | @override 19 | Db get db => conf.db; 20 | 21 | @override 22 | DbTable get table => manufacturerTable; 23 | 24 | @override 25 | Map toDb() => {"name": name}; 26 | 27 | @override 28 | Manufacturer fromDb(Map map) => 29 | Manufacturer(name: map["name"].toString()); 30 | } 31 | 32 | To set a foreign key mention it in your table schema: 33 | 34 | :: 35 | 36 | final carTable = DbTable("car") 37 | ..varchar("name") 38 | ..real("price") 39 | ..foreign_key("manufacturer"); 40 | 41 | Update the serializers in the main model to use the foreign key: 42 | 43 | :: 44 | 45 | class Car with DbModel { 46 | @override 47 | Map toDb() { 48 | final row = { 49 | // ... 50 | "manufacturer": manufacturer.id 51 | }; 52 | return row; 53 | } 54 | 55 | @override 56 | Car fromDb(Map map) { 57 | final car = Car( 58 | // ... 59 | ); 60 | // the key will be present only with join queries 61 | // in a simple select this data is not present 62 | if (map.containsKey("manufacturer")) { 63 | car.manufacturer = 64 | Manufacturer().fromDb(map["manufacturer"] as Map); 65 | } 66 | return car; 67 | } 68 | } 69 | 70 | To perform a join query: 71 | 72 | :: 73 | 74 | class Car with DbModel { 75 | static Future> selectRelated({String where, int limit}) async { 76 | final cars = List.from( 77 | await Car().sqlJoin(where: where, limit: limit)); 78 | return cars; 79 | } 80 | } 81 | 82 | And then use it: 83 | 84 | 85 | :: 86 | 87 | List cars = await Car.selectRelated(where: "price<50000"); 88 | print(cars[0].manufacturer.name); 89 | -------------------------------------------------------------------------------- /docs/dbmodels/init.rst: -------------------------------------------------------------------------------- 1 | Declare the model 2 | ================= 3 | 4 | It is possible to use a mixin to extend a custom model and give it database 5 | interaction methods. This way when querying the database no deserializing and 6 | type casts are needed: only model objects are used 7 | 8 | Extend with DbModel 9 | ------------------- 10 | 11 | .. highlight:: dart 12 | 13 | :: 14 | 15 | class Car with DbModel { 16 | String name; 17 | double price; 18 | } 19 | 20 | Override getters 21 | ---------------- 22 | 23 | :: 24 | 25 | class Car with DbModel { 26 | @override 27 | int id; 28 | 29 | @override 30 | Db get db => conf.db; 31 | 32 | @override 33 | DbTable get table => carTable; 34 | } 35 | 36 | ``conf.db`` is the ``Db`` object used. ``carTable`` is the car table schema 37 | 38 | Declare a schema 39 | ---------------- 40 | 41 | :: 42 | 43 | final carTable = DbTable("car") 44 | ..varchar("name") 45 | ..integer("max_speed") 46 | ..real("price") 47 | ..integer("year") 48 | ..boolean("is_4wd", defaultValue: false); 49 | 50 | Include this schema in your database initialization call: 51 | 52 | :: 53 | 54 | db.init(path: "db.sqlite", schema: [carTable]); 55 | 56 | Define serializers 57 | ------------------ 58 | 59 | The ``toDb`` serializer and ``fromDb`` deserializer must be defined 60 | 61 | :: 62 | 63 | class Car with DbModel { 64 | @override 65 | Map toDb() { 66 | final row = { 67 | "name": name, 68 | "max_speed": maxSpeed, 69 | "price": price, 70 | "year": year.millisecondsSinceEpoch, 71 | "is_4wd": is4wd, 72 | "manufacturer": manufacturer.id 73 | }; 74 | return row; 75 | } 76 | 77 | @override 78 | Car fromDb(Map map) { 79 | final car = Car( 80 | id: map["id"] as int, 81 | name: map["name"].toString(), 82 | maxSpeed: map["max_speed"] as int, 83 | price: map["price"] as double, 84 | year: DateTime.fromMillisecondsSinceEpoch(map["year"] as int), 85 | is4wd: (map["is_4wd"].toString() == "true"), 86 | ); 87 | return car; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/dbmodels/mutate.rst: -------------------------------------------------------------------------------- 1 | Data mutations 2 | ============== 3 | 4 | Once properly declared the model can be modified in the database 5 | 6 | Insert 7 | ------ 8 | 9 | .. highlight:: dart 10 | 11 | :: 12 | 13 | final car = Car(name: "My car", price: 25000.0); 14 | car.sqlInsert(); 15 | 16 | 17 | Update 18 | ------ 19 | 20 | :: 21 | 22 | car.price = 23000.0; 23 | car.sqlUpdate(); 24 | 25 | Upsert 26 | ------ 27 | 28 | :: 29 | 30 | car.name = "My new car name"; 31 | car.sqlUpsert(); 32 | 33 | Delete 34 | ------ 35 | 36 | :: 37 | 38 | car.sqlDelete(); 39 | 40 | 41 | The query parameters are the same than for regular queries: check the database 42 | operations section for details 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/dbmodels/select.rst: -------------------------------------------------------------------------------- 1 | Select operations 2 | ================= 3 | 4 | The select calls are done via an instance of the model. The recommended method 5 | is to define some select static methods in your model: 6 | 7 | .. highlight:: dart 8 | 9 | :: 10 | 11 | class Car with DbModel { 12 | static Future> select({String where, int limit}) async { 13 | final cars = List.from( 14 | await Car().sqlSelect(where: where, limit: limit)); 15 | return cars; 16 | } 17 | } 18 | 19 | And then use it: 20 | 21 | 22 | :: 23 | 24 | List cars = await Car.select(where: "price<50000"); 25 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Sqlcool documentation master file 2 | 3 | Welcome to Sqlcool's documentation! 4 | =================================== 5 | 6 | The objectives of this lib is to provide a simple api while staying 7 | close to sql. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Crud operations 12 | 13 | schema.rst 14 | init.rst 15 | db_ops.rst 16 | batch_ops.rst 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | :caption: Reactivity 21 | 22 | bloc.rst 23 | changefeed.rst 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | :caption: Database models 28 | 29 | dbmodels/init.rst 30 | dbmodels/mutate.rst 31 | dbmodels/select.rst 32 | dbmodels/foreign_keys.rst 33 | 34 | Indices and tables 35 | ================== 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | -------------------------------------------------------------------------------- /docs/init.rst: -------------------------------------------------------------------------------- 1 | Initialize database 2 | =================== 3 | 4 | Initialize an empty database 5 | ---------------------------- 6 | 7 | .. highlight:: dart 8 | 9 | :: 10 | 11 | import 'package:sqlcool/sqlcool.dart'; 12 | 13 | Db db = Db(); 14 | 15 | // either use the schema definition constructor 16 | // or define the tables by hand 17 | void myInit() { 18 | String q1 = """CREATE TABLE category ( 19 | id INTEGER PRIMARY KEY, 20 | name TEXT NOT NULL 21 | )"""; 22 | String q2 = """CREATE TABLE product ( 23 | id INTEGER PRIMARY KEY, 24 | name TEXT NOT NULL, 25 | price REAL NOT NULL, 26 | category_id INTEGER, 27 | CONSTRAINT category 28 | FOREIGN KEY (category_id) 29 | REFERENCES category(id) 30 | ON DELETE CASCADE 31 | )"""; 32 | // the path is relative to the documents directory 33 | String dbpath = "data.sqlite"; 34 | List queries = [q1, q2]; 35 | db.init(path: dbpath, queries: queries, verbose: true).catchError((e) { 36 | throw("Error initializing the database: $e"); 37 | }); 38 | } 39 | 40 | void main() { 41 | /// initialize the database async. Use the [onReady] 42 | /// callback later to react to the initialization completed event 43 | myInit(); 44 | runApp(MyApp()); 45 | } 46 | 47 | // then later check if the database is ready 48 | 49 | @override 50 | void initState() { 51 | db.onReady.then((_) { 52 | setState(() { 53 | print("STATE: THE DATABASE IS READY"); 54 | }); 55 | }); 56 | super.initState(); 57 | } 58 | 59 | Required parameters for ``init``: 60 | 61 | :path: *String* path where the database file will be stored: 62 | relative to the documents directory path 63 | 64 | Optional parameter: 65 | 66 | :sqfliteDatabase: *Database* an optional existing Sqflite database 67 | :queries: *List* queries to run at database creation 68 | :fromAsset: *String* path to the Sqlite asset file, relative to the 69 | documents directory 70 | :absolutePath: *bool* if `true` the provided path will not be relative to the 71 | documents directory and taken as absolute 72 | :verbose: *bool* ``true`` or ``false`` 73 | 74 | The database is created in the documents directory. 75 | The create table queries will run once on database file creation. 76 | 77 | Initialize a database from an Sqlite asset file 78 | ----------------------------------------------- 79 | 80 | :: 81 | 82 | void main() { 83 | String dbpath = "data.sqlite"; 84 | db.init(path: dbpath, fromAsset: "assets/data.sqlite", verbose: true).catchError((e) { 85 | print("Error initializing the database; $e"); 86 | }); 87 | } 88 | 89 | Multiple databases 90 | ------------------ 91 | 92 | :: 93 | 94 | import 'package:sqlcool/sqlcool.dart'; 95 | 96 | void main() { 97 | db1 = Db(); 98 | db2 = Db(); 99 | // ... 100 | } 101 | 102 | Verbosity 103 | --------- 104 | 105 | The ``Db`` methods have a ``verbose`` option that will print the query. To get more 106 | detailled information and queries results you can activate the Sqflite debug mode: 107 | 108 | 109 | :: 110 | 111 | db.init(path: dbpath, queries: [q], debug: true); -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=Sqlcool 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/schema.rst: -------------------------------------------------------------------------------- 1 | Schema definition 2 | ================= 3 | 4 | Columns 5 | ------- 6 | 7 | .. highlight:: dart 8 | 9 | :: 10 | 11 | DbTable category = DbTable("category")..varchar("name", unique: true); 12 | DbTable product = DbTable("product") 13 | ..varchar("name", unique: true) 14 | ..integer("price") 15 | ..real("number") 16 | ..boolean("bool", defaultValue: true) 17 | ..text("description") 18 | ..blob("blob") 19 | ..timestamp() 20 | ..foreignKey("category", onDelete: OnDelete.cascade); 21 | 22 | Parameters for the column constructors: 23 | 24 | :name: *String* the name of the column 25 | 26 | Optional parameters: 27 | 28 | :unique: *bool* if the column must be unique 29 | :nullable: *bool* if the column can be null 30 | :defaultValue: *dynamic* (depending on the row type: integer if 31 | the row is integer for example) the default value of a column 32 | :check: *String* a check constraint: ex: 33 | 34 | :: 35 | 36 | DbTable("table")..integer("intname", check="intname>0"); 37 | 38 | Note: the foreignKey must be placed after the other fields definitions 39 | 40 | Create an index on a column: 41 | 42 | :: 43 | 44 | DbTable("table") 45 | ..varchar("name") 46 | ..index("name"); 47 | 48 | Unique together constraint: 49 | 50 | :: 51 | 52 | DbTable("table") 53 | ..varchar("name") 54 | ..integer("number") 55 | ..uniqueTogether("name", "number"); 56 | 57 | Methods 58 | ------- 59 | 60 | Initialize the database with a schema: 61 | 62 | :: 63 | 64 | db.init(path: "mydb.sqlite", schema: [category, product]); 65 | 66 | Check if the database has a schema: 67 | 68 | :: 69 | 70 | final bool hasSchema = db.hasSchema() // true or false; 71 | 72 | Get a table schema: 73 | 74 | :: 75 | 76 | final DbTable productSchema = db.schema.table("product"); 77 | 78 | Check if a table is in the schema: 79 | 80 | :: 81 | 82 | final bool tableExists = db.schema.hasTable("product"); 83 | 84 | Check if a table has a column: 85 | 86 | :: 87 | 88 | final bool columnExists = db.schema.table("product").hasColumn("name"); 89 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:extra_pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | strong-mode: 5 | implicit-casts: false 6 | implicit-dynamic: false 7 | errors: 8 | missing_return: error 9 | missing_required_param: error 10 | invalid_use_of_protected_member: error 11 | dead_code: info 12 | sdk_version_async_exported_from_core: ignore 13 | linter: 14 | rules: 15 | - unnecessary_statements 16 | - unnecessary_lambdas 17 | - avoid_classes_with_only_static_members 18 | - avoid_renaming_method_parameters 19 | - camel_case_types 20 | - constant_identifier_names 21 | - cascade_invocations 22 | - omit_local_variable_types 23 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/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/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | AppBar appBar(BuildContext context, {String title = "Sqlcool example"}) { 4 | return AppBar( 5 | title: Text(title), 6 | actions: [ 7 | IconButton( 8 | tooltip: "View the database", 9 | icon: Icon(Icons.view_list), 10 | onPressed: () => Navigator.of(context).pushNamed("/dbmanager"), 11 | ) 12 | ], 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /example/lib/conf.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | final SqlDb db = SqlDb(); 4 | final Db db2 = Db(); 5 | -------------------------------------------------------------------------------- /example/lib/database.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | import 'conf.dart'; 4 | 5 | const table = "product"; 6 | 7 | Future saveItem(String itemName) async { 8 | final row = DbRow(>[ 9 | DbRecord("name", itemName), 10 | DbRecord("category", 1), 11 | DbRecord("price", 50), 12 | ]); 13 | await db 14 | .insert(table: table, row: row, verbose: true) 15 | .catchError((dynamic e) { 16 | throw e; 17 | }); 18 | } 19 | 20 | Future deleteItem(int itemId) async { 21 | await db 22 | .delete(table: table, where: 'id="$itemId"', verbose: true) 23 | .catchError((dynamic e) { 24 | throw e; 25 | }); 26 | } 27 | 28 | Future updateItem(String oldItemName, String newItemName) async { 29 | final row = DbRow.fromRecord(DbRecord("name", newItemName)); 30 | await db 31 | .update( 32 | table: table, where: 'name="$oldItemName"', row: row, verbose: true) 33 | .catchError((dynamic e) { 34 | throw e; 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/dbmodels/db.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../conf.dart'; 4 | import 'models/car.dart'; 5 | import 'models/manufacturer.dart'; 6 | 7 | Future populateDb() async { 8 | print("SCHEMA TABLES ${db.schema.tables}"); 9 | final q = await db.query( 10 | "SELECT name FROM sqlite_master WHERE type ='table' AND name NOT LIKE 'sqlite_%';"); 11 | print("T $q"); 12 | final n = await db.count(table: "car"); 13 | final hasData = n > 0; 14 | if (hasData) { 15 | print("Car table has data"); 16 | return; 17 | } 18 | print("Populating cars table"); 19 | final m1 = Manufacturer(name: "Fiat"); 20 | final m2 = Manufacturer(name: "General Motors"); 21 | m1.id = await m1.sqlInsert(verbose: true); 22 | m2.id = await m2.sqlInsert(verbose: true); 23 | final c1 = Car( 24 | name: "Car 1", 25 | price: 13000.0, 26 | maxSpeed: 200, 27 | year: DateTime.now().subtract(const Duration(days: 360 * 5)), 28 | is4wd: true, 29 | manufacturer: m1); 30 | final c2 = Car( 31 | name: "Car 2", 32 | price: 15000.0, 33 | maxSpeed: 220, 34 | is4wd: false, 35 | year: DateTime.now().subtract(const Duration(days: 360 * 3)), 36 | manufacturer: m1); 37 | final c3 = Car( 38 | name: "Car 3", 39 | price: 23000.0, 40 | maxSpeed: 260, 41 | is4wd: false, 42 | year: DateTime.now().subtract(const Duration(days: 360)), 43 | manufacturer: m2); 44 | await c1.sqlInsert(verbose: true); 45 | await c2.sqlInsert(verbose: true); 46 | await c3.sqlInsert(verbose: true); 47 | } 48 | -------------------------------------------------------------------------------- /example/lib/dbmodels/dbmodels.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'models/car.dart'; 3 | import 'db.dart'; 4 | 5 | class _DbModelPageState extends State { 6 | var cars = []; 7 | 8 | Future> initModel() async { 9 | // populate db if needed 10 | await populateDb(); 11 | // perform a join query on the database for initial data 12 | final c = await Car.selectRelated(); 13 | print("Found ${c.length} cars: $c"); 14 | return c; 15 | } 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | initModel().then((c) => setState(() => cars = c)); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar(title: Text("Db models")), 27 | body: ListView.builder( 28 | itemCount: cars.length, 29 | itemBuilder: (BuildContext context, int i) { 30 | final car = cars[i]; 31 | return ListTile( 32 | title: Text("${car.name}"), 33 | subtitle: Text("Manufacturer: ${car.manufacturer.name}"), 34 | trailing: IconButton( 35 | icon: Icon(Icons.delete), 36 | onPressed: () async { 37 | await car.sqlDelete(verbose: true); 38 | // refresh 39 | final c = await Car.selectRelated(); 40 | setState(() => cars = c); 41 | }, 42 | ), 43 | ); 44 | }, 45 | ), 46 | ); 47 | } 48 | } 49 | 50 | class DbModelPage extends StatefulWidget { 51 | @override 52 | _DbModelPageState createState() => _DbModelPageState(); 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/dbmodels/models/car.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | import '../../conf.dart' as conf; 4 | import '../schema.dart'; 5 | import 'manufacturer.dart'; 6 | 7 | class Car with DbModel { 8 | Car( 9 | {this.id, 10 | this.name, 11 | this.maxSpeed, 12 | this.price, 13 | this.year, 14 | this.is4wd, 15 | this.manufacturer}); 16 | 17 | /// define some class properties 18 | 19 | final String name; 20 | final int maxSpeed; 21 | final double price; 22 | final DateTime year; 23 | final bool is4wd; 24 | Manufacturer manufacturer; 25 | 26 | /// [DbModel] required overrides 27 | 28 | @override 29 | int id; 30 | 31 | /// the [Db] used 32 | @override 33 | Db get db => conf.db2; 34 | 35 | /// the table schema representation 36 | /// check example/pages/dbmodels/schema.dart 37 | @override 38 | DbTable get table => carTable; 39 | 40 | /// serialize a row to the database 41 | @override 42 | Map toDb() { 43 | // we want the foreign key to be recorded 44 | assert(manufacturer?.id != null); 45 | final row = { 46 | "name": name, 47 | "max_speed": maxSpeed, 48 | "price": price, 49 | "year": year.millisecondsSinceEpoch, 50 | "is_4wd": is4wd, 51 | "manufacturer": manufacturer.id 52 | }; 53 | return row; 54 | } 55 | 56 | /// deserialize a row from database 57 | @override 58 | Car fromDb(Map map) { 59 | final car = Car( 60 | id: map["id"] as int, 61 | name: map["name"].toString(), 62 | maxSpeed: map["max_speed"] as int, 63 | price: map["price"] as double, 64 | year: DateTime.fromMillisecondsSinceEpoch(map["year"] as int), 65 | is4wd: (map["is_4wd"].toString() == "true"), 66 | ); 67 | // the key will be present only with join queries 68 | // in a simple select this data is not present 69 | if (map.containsKey("manufacturer")) { 70 | car.manufacturer = 71 | Manufacturer().fromDb(map["manufacturer"] as Map); 72 | } 73 | return car; 74 | } 75 | 76 | /// Create a static join method for convenience 77 | 78 | static Future> selectRelated({String where, int limit}) async { 79 | final cars = List.from( 80 | await Car().sqlJoin(where: where, limit: limit, verbose: true)); 81 | return cars; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/lib/dbmodels/models/manufacturer.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | import '../../conf.dart' as conf; 4 | import '../schema.dart'; 5 | 6 | class Manufacturer with DbModel { 7 | Manufacturer({this.id, this.name}); 8 | 9 | final String name; 10 | 11 | /// [DbModel] required overrides 12 | 13 | @override 14 | int id; 15 | 16 | @override 17 | Db get db => conf.db2; 18 | 19 | @override 20 | DbTable get table => manufacturerTable; 21 | 22 | @override 23 | 24 | /// we do not set [id] and let the database create it 25 | /// and manage it's primary keys automatically 26 | Map toDb() => {"name": name}; 27 | 28 | @override 29 | Manufacturer fromDb(Map map) { 30 | return Manufacturer(id: map["id"] as int, name: map["name"].toString()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/dbmodels/schema.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | final carTable = DbTable("car") 4 | ..varchar("name") 5 | ..integer("max_speed") 6 | ..real("price") 7 | ..integer("year") 8 | ..boolean("is_4wd", defaultValue: false) 9 | ..foreignKey("manufacturer", onDelete: OnDelete.cascade); 10 | 11 | final manufacturerTable = DbTable("manufacturer")..varchar("name"); 12 | -------------------------------------------------------------------------------- /example/lib/dbviewer/dbviewer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | 4 | import '../appbar.dart'; 5 | import 'table.dart'; 6 | 7 | class _DbViewerState extends State { 8 | _DbViewerState({@required this.db}); 9 | 10 | final SqlDb db; 11 | 12 | Map _tableNumRows; 13 | var _ready = false; 14 | 15 | Future> countRows() async { 16 | await db.onReady; 17 | final tnr = {}; 18 | for (final table in db.schema.tables) { 19 | print("NR COUNT ${table.name}"); 20 | final v = await db.count(table: table.name); 21 | tnr[table] = v; 22 | } 23 | print("NR $tnr"); 24 | return tnr; 25 | } 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | countRows().then((tnr) => setState(() { 31 | _tableNumRows = tnr; 32 | _ready = true; 33 | })); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | if (!_ready) { 39 | return const Center(child: CircularProgressIndicator()); 40 | } 41 | final rows = []; 42 | _tableNumRows.forEach((table, n) => rows.add(GestureDetector( 43 | child: ListTile( 44 | title: Text("${table.name}"), 45 | trailing: Text("$n rows"), 46 | onTap: () => Navigator.of(context).push( 47 | MaterialPageRoute(builder: (BuildContext context) { 48 | return DbViewerTable(db: db, table: table); 49 | })), 50 | )))); 51 | return Scaffold( 52 | appBar: appBar(context, title: "Tables"), 53 | body: ListView(children: rows)); 54 | } 55 | } 56 | 57 | class DbViewer extends StatefulWidget { 58 | DbViewer({@required this.db}) 59 | : assert(db.hasSchema, 60 | "The database has no schema, the viewer is unavailable"); 61 | 62 | final SqlDb db; 63 | 64 | @override 65 | _DbViewerState createState() => _DbViewerState(db: db); 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/dbviewer/table.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | import '../appbar.dart'; 4 | 5 | class _DbViewerTableState extends State { 6 | _DbViewerTableState({@required this.db, @required this.table}); 7 | 8 | final SqlDb db; 9 | final DbTable table; 10 | 11 | List _rows; 12 | var _ready = false; 13 | 14 | Future _getData() async { 15 | _rows = await db.select(table: table.name, limit: 100).catchError( 16 | (dynamic e) => 17 | throw Exception("Can not select from table ${table.name}")); 18 | } 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _getData().then((_) => setState(() => _ready = true)); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return _ready 29 | ? Scaffold( 30 | appBar: appBar(context, title: "Table ${table.name}"), 31 | body: ListView.builder( 32 | itemCount: _rows.length, 33 | itemBuilder: (BuildContext context, int index) { 34 | final row = _rows[index]; 35 | return ListTile(title: Text("${row.line()}")); 36 | }, 37 | )) 38 | : const Center( 39 | child: CircularProgressIndicator(), 40 | ); 41 | } 42 | } 43 | 44 | class DbViewerTable extends StatefulWidget { 45 | const DbViewerTable({@required this.db, @required this.table}); 46 | 47 | final SqlDb db; 48 | final DbTable table; 49 | 50 | @override 51 | _DbViewerTableState createState() => 52 | _DbViewerTableState(db: db, table: table); 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/dialogs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'database.dart'; 3 | 4 | void insertItemDialog(BuildContext context) { 5 | final nameController = TextEditingController(); 6 | showDialog( 7 | context: context, 8 | builder: (BuildContext context) { 9 | return AlertDialog( 10 | title: const Text("Add an item"), 11 | actions: [ 12 | FlatButton( 13 | child: const Text("Cancel"), 14 | onPressed: () { 15 | Navigator.of(context).pop(true); 16 | }, 17 | ), 18 | FlatButton( 19 | child: const Text("Save"), 20 | onPressed: () { 21 | final txt = nameController.text; 22 | saveItem(txt).catchError((dynamic e) { 23 | throw ("Can not save item ${e.message}"); 24 | }); 25 | Navigator.of(context).pop(true); 26 | }, 27 | ), 28 | ], 29 | content: TextField( 30 | controller: nameController, 31 | autofocus: true, 32 | ), 33 | ); 34 | }, 35 | ); 36 | } 37 | 38 | void deleteItemDialog(BuildContext context, String itemName, int itemId) { 39 | showDialog( 40 | context: context, 41 | builder: (BuildContext context) { 42 | return AlertDialog( 43 | title: Text("Delete $itemName?"), 44 | actions: [ 45 | FlatButton( 46 | child: const Text("Cancel"), 47 | onPressed: () { 48 | Navigator.of(context).pop(); 49 | }, 50 | ), 51 | RaisedButton( 52 | child: const Text("Delete"), 53 | color: Colors.red, 54 | onPressed: () { 55 | deleteItem(itemId).catchError((dynamic e) { 56 | throw (e); 57 | }); 58 | Navigator.of(context).pop(true); 59 | }, 60 | ), 61 | ], 62 | ); 63 | }, 64 | ); 65 | } 66 | 67 | void updateItemDialog(BuildContext context, String itemName) { 68 | final nameController = TextEditingController(text: itemName); 69 | 70 | showDialog( 71 | context: context, 72 | builder: (BuildContext context) { 73 | return AlertDialog( 74 | title: const Text("Update item"), 75 | content: TextField( 76 | controller: nameController, 77 | autofocus: true, 78 | ), 79 | actions: [ 80 | FlatButton( 81 | child: const Text("Cancel"), 82 | onPressed: () { 83 | Navigator.of(context).pop(true); 84 | }, 85 | ), 86 | FlatButton( 87 | child: const Text("Save"), 88 | onPressed: () { 89 | final txt = nameController.text; 90 | updateItem(itemName, txt).catchError((dynamic e) { 91 | throw ("Can not update category $e"); 92 | }); 93 | Navigator.of(context).pop(true); 94 | }, 95 | ), 96 | ], 97 | ); 98 | }, 99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /example/lib/init_db.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | import 'dbmodels/schema.dart'; 4 | 5 | Future initDb( 6 | {@required SqlDb db, 7 | String path = "items2.sqlite", 8 | bool absPath = false}) async { 9 | // define the tables 10 | final category = DbTable("category")..varchar("name", unique: true); 11 | final product = DbTable("product") 12 | ..varchar("name", unique: true) 13 | ..integer("price") 14 | ..foreignKey("category", onDelete: OnDelete.cascade) 15 | ..index("name"); 16 | // prepare the queries 17 | final populateQueries = [ 18 | 'INSERT INTO category(name) VALUES("Category 1")', 19 | 'INSERT INTO category(name) VALUES("Category 2")', 20 | 'INSERT INTO category(name) VALUES("Category 3")', 21 | 'INSERT INTO product(name,price,category) VALUES("Product 1", 50, 1)', 22 | 'INSERT INTO product(name,price,category) VALUES("Product 2", 30, 1)', 23 | 'INSERT INTO product(name,price,category) VALUES("Product 3", 20, 2)' 24 | ]; 25 | // initialize the database 26 | await db 27 | .init( 28 | path: path, 29 | schema: [ 30 | category, product, 31 | // db models 32 | manufacturerTable, 33 | carTable, 34 | ], 35 | queries: populateQueries, 36 | absolutePath: absPath, 37 | verbose: true) 38 | .catchError((dynamic e) { 39 | throw Exception("Error initializing the database: ${e.message}"); 40 | }); 41 | print("Database initialized with schema:"); 42 | //db.schema.describe(); 43 | print("SCHEMA TABLES ${db.schema.tables}"); 44 | final q = await db.query( 45 | "SELECT name FROM sqlite_master WHERE type ='table' AND name NOT LIKE 'sqlite_%';"); 46 | print("T $q"); 47 | } 48 | 49 | Future initDb2( 50 | {@required Db db, 51 | String path = "items.sqlite", 52 | bool absPath = false}) async { 53 | // define the tables 54 | final category = DbTable("category")..varchar("name", unique: true); 55 | final product = DbTable("product") 56 | ..varchar("name", unique: true) 57 | ..integer("price") 58 | ..foreignKey("category", onDelete: OnDelete.cascade) 59 | ..index("name"); 60 | // prepare the queries 61 | final populateQueries = [ 62 | 'INSERT INTO category(name) VALUES("Category 1")', 63 | 'INSERT INTO category(name) VALUES("Category 2")', 64 | 'INSERT INTO category(name) VALUES("Category 3")', 65 | 'INSERT INTO product(name,price,category) VALUES("Product 1", 50, 1)', 66 | 'INSERT INTO product(name,price,category) VALUES("Product 2", 30, 1)', 67 | 'INSERT INTO product(name,price,category) VALUES("Product 3", 20, 2)' 68 | ]; 69 | // initialize the database 70 | await db 71 | .init( 72 | path: path, 73 | schema: [ 74 | category, 75 | product, 76 | // db models 77 | manufacturerTable, 78 | carTable, 79 | ], 80 | queries: populateQueries, 81 | absolutePath: absPath, 82 | verbose: true) 83 | .catchError((dynamic e) { 84 | throw ("Error initializing the database: ${e.message}"); 85 | }); 86 | //print("Database initialized with schema:"); 87 | //db.schema.describe(); 88 | } 89 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'conf.dart'; 4 | import 'dbmodels/dbmodels.dart'; 5 | import 'dbviewer/dbviewer.dart'; 6 | import 'init_db.dart'; 7 | import 'pages/index.dart'; 8 | import 'pages/join.dart'; 9 | import 'pages/select_bloc.dart'; 10 | import 'pages/upsert.dart'; 11 | 12 | void main() { 13 | runApp(MyApp()); 14 | 15 | /// initialize the database async. We will use the [onReady] 16 | /// callback later to react to the initialization completed event 17 | initDb(db: db); 18 | initDb2(db: db2); 19 | } 20 | 21 | final routes = { 22 | '/': (BuildContext context) => PageIndex(), 23 | '/select_bloc': (BuildContext context) => PageSelectBloc(), 24 | '/join': (BuildContext context) => PageJoinQuery(), 25 | '/upsert': (BuildContext context) => UpsertPage(), 26 | '/dbmodel': (BuildContext context) => DbModelPage(), 27 | '/dbmanager': (BuildContext context) => DbViewer(db: db), 28 | }; 29 | 30 | class MyApp extends StatelessWidget { 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | debugShowCheckedModeBanner: false, 35 | title: 'Sqlcool example', 36 | routes: routes, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/pages/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../appbar.dart'; 4 | import '../conf.dart'; 5 | 6 | class _PageIndexState extends State { 7 | bool databaseIsReady = false; 8 | 9 | @override 10 | void initState() { 11 | db.onReady.then((_) async { 12 | await db2.onReady; 13 | print("STATE: THE DATABASE IS READY"); 14 | setState(() { 15 | databaseIsReady = true; 16 | }); 17 | }); 18 | super.initState(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return !databaseIsReady 24 | ? const Scaffold( 25 | body: Center(child: Text("The database is initializing ..."))) 26 | : Scaffold( 27 | appBar: appBar(context), 28 | body: Padding( 29 | padding: const EdgeInsets.only(top: 25.0), 30 | child: GridView( 31 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 32 | crossAxisCount: 2), 33 | children: [ 34 | ExampleTile( 35 | title: "Select bloc", 36 | iconData: Icons.select_all, 37 | route: "/select_bloc", 38 | ), 39 | ExampleTile( 40 | title: "Upsert", 41 | iconData: Icons.system_update_alt, 42 | route: "/upsert", 43 | ), 44 | ExampleTile( 45 | title: "Join query", 46 | iconData: Icons.view_module, 47 | route: "/join", 48 | ), 49 | ExampleTile( 50 | title: "Db model", 51 | iconData: Icons.content_paste, 52 | route: "/dbmodel", 53 | ), 54 | ], 55 | ))); 56 | } 57 | } 58 | 59 | class PageIndex extends StatefulWidget { 60 | @override 61 | _PageIndexState createState() => _PageIndexState(); 62 | } 63 | 64 | class ExampleTile extends StatelessWidget { 65 | const ExampleTile( 66 | {@required this.iconData, @required this.title, @required this.route}); 67 | 68 | final IconData iconData; 69 | final String title; 70 | final String route; 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return GestureDetector( 75 | child: Column(children: [ 76 | Icon(iconData, size: 65.0, color: Colors.grey), 77 | Padding(padding: const EdgeInsets.all(5.0), child: Text(title)), 78 | ]), 79 | onTap: () => Navigator.of(context).pushNamed(route), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /example/lib/pages/join.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:sqlcool/sqlcool.dart'; 4 | import '../conf.dart'; 5 | 6 | class _PageJoinQueryState extends State { 7 | final _streamController = StreamController>(); 8 | 9 | @override 10 | void initState() { 11 | db 12 | .join( 13 | table: "product", 14 | columns: "product.name, price, category.name as category_name", 15 | joinTable: "category", 16 | joinOn: "product.category = category.id", 17 | verbose: true, 18 | ) 19 | .then((items) { 20 | _streamController.sink.add(items); 21 | }); 22 | super.initState(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | _streamController.close(); 28 | super.dispose(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar(title: Text("Join query")), 35 | body: StreamBuilder( 36 | stream: _streamController.stream, 37 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 38 | if (snapshot.hasData) { 39 | return ListView.builder( 40 | itemCount: snapshot.data.length, 41 | itemBuilder: (BuildContext context, int index) { 42 | final row = snapshot.data[index]; 43 | final price = row.record("price"); 44 | return ListTile( 45 | title: Text(row.record("name")), 46 | subtitle: Text(row.record("category_name")), 47 | trailing: (price != null) ? Text("$price") : const Text(""), 48 | ); 49 | }, 50 | ); 51 | } else { 52 | return const CircularProgressIndicator(); 53 | } 54 | }, 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | class PageJoinQuery extends StatefulWidget { 61 | @override 62 | _PageJoinQueryState createState() => _PageJoinQueryState(); 63 | } 64 | -------------------------------------------------------------------------------- /example/lib/pages/select_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:sqlcool/sqlcool.dart'; 5 | 6 | import '../conf.dart'; 7 | import '../dialogs.dart'; 8 | 9 | class _PageSelectBlocState extends State { 10 | SqlSelectBloc bloc; 11 | StreamSubscription _changefeed; 12 | 13 | @override 14 | void initState() { 15 | // declare the query 16 | this.bloc = SqlSelectBloc( 17 | database: db, table: "product", orderBy: 'name ASC', reactive: true); 18 | // listen for changes in the database 19 | _changefeed = db.changefeed.listen((change) { 20 | print("CHANGE IN THE DATABASE:"); 21 | print("Change type: ${change.type}"); 22 | print("Number of items impacted: ${change.value}"); 23 | print("Query: ${change.query}"); 24 | if (change.type == DatabaseChange.update) { 25 | print("${change.value} items updated"); 26 | } 27 | }); 28 | super.initState(); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | _changefeed.cancel(); 34 | bloc.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Scaffold( 41 | appBar: AppBar(title: const Text("Select bloc")), 42 | body: StreamBuilder>( 43 | stream: bloc.rows, 44 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 45 | if (snapshot.hasData) { 46 | // the select query has not found anything 47 | if (snapshot.data.isEmpty) { 48 | return const Center( 49 | child: Text("No data. Use the + button to insert an item"), 50 | ); 51 | } 52 | // the select query has results 53 | return ListView.builder( 54 | itemCount: snapshot.data.length, 55 | itemBuilder: (BuildContext context, int index) { 56 | final row = snapshot.data[index]; 57 | final name = row.record("name"); 58 | final id = row.record("id"); 59 | return ListTile( 60 | title: GestureDetector( 61 | child: Text(name), 62 | onTap: () => updateItemDialog(context, name), 63 | ), 64 | trailing: IconButton( 65 | icon: const Icon(Icons.delete), 66 | color: Colors.grey, 67 | onPressed: () => deleteItemDialog(context, name, id), 68 | ), 69 | ); 70 | }); 71 | } else { 72 | // the select query is still running 73 | return const CircularProgressIndicator(); 74 | } 75 | }), 76 | floatingActionButton: FloatingActionButton( 77 | child: const Icon(Icons.add), 78 | onPressed: () => insertItemDialog(context), 79 | ), 80 | ); 81 | } 82 | } 83 | 84 | class PageSelectBloc extends StatefulWidget { 85 | @override 86 | _PageSelectBlocState createState() => _PageSelectBlocState(); 87 | } 88 | -------------------------------------------------------------------------------- /example/lib/pages/upsert.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:sqlcool/sqlcool.dart'; 4 | import '../conf.dart'; 5 | 6 | class _UpsertPageState extends State { 7 | SqlSelectBloc bloc; 8 | int numProducts; 9 | 10 | final Random r = Random(); 11 | 12 | @override 13 | void initState() { 14 | bloc = SqlSelectBloc( 15 | database: db, 16 | table: "product", 17 | columns: "name,price", 18 | orderBy: 'name ASC', 19 | reactive: true); 20 | db.count(table: "product").then((n) => numProducts = n); 21 | super.initState(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | bloc.dispose(); 27 | super.dispose(); 28 | } 29 | 30 | Future upsertAdd() async { 31 | final price = r.nextInt(100); 32 | await db.upsert( 33 | table: "product", 34 | row: DbRow(>[ 35 | DbRecord("name", "Product ${numProducts + 1}"), 36 | DbRecord("price", price), 37 | DbRecord("category", 1) 38 | ]), 39 | verbose: true); 40 | await db.count(table: "product").then((n) => numProducts = n); 41 | } 42 | 43 | Future upsertUpdate() async { 44 | final n = r.nextInt(100); 45 | await db.upsert( 46 | table: "product", 47 | row: DbRow(>[ 48 | DbRecord("name", "Product 1"), 49 | DbRecord("price", n), 50 | DbRecord("category", 1) 51 | ]), 52 | preserveColumns: ["category"], 53 | indexColumn: "name", 54 | verbose: true); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | appBar: AppBar(title: const Text("Upsert")), 61 | body: Column( 62 | children: [ 63 | const Padding(padding: EdgeInsets.only(top: 10.0)), 64 | Row( 65 | mainAxisAlignment: MainAxisAlignment.center, 66 | children: [ 67 | RaisedButton( 68 | child: const Text("Add"), 69 | onPressed: upsertAdd, 70 | ), 71 | const Padding(padding: EdgeInsets.symmetric(horizontal: 5.0)), 72 | RaisedButton( 73 | child: const Text("Update"), 74 | onPressed: upsertUpdate, 75 | ), 76 | ], 77 | ), 78 | Expanded( 79 | child: StreamBuilder( 80 | stream: bloc.rows, 81 | builder: 82 | (BuildContext context, AsyncSnapshot> snapshot) { 83 | if (snapshot.hasData) { 84 | return ListView.builder( 85 | itemCount: snapshot.data.length, 86 | itemBuilder: (BuildContext context, int index) { 87 | final row = snapshot.data[index]; 88 | return ListTile( 89 | title: Text(row.record("name")), 90 | trailing: Text("${row.record("price")}"), 91 | ); 92 | }, 93 | ); 94 | } else { 95 | return const Center(child: CircularProgressIndicator()); 96 | } 97 | }, 98 | ), 99 | ) 100 | ], 101 | ), 102 | ); 103 | } 104 | } 105 | 106 | class UpsertPage extends StatefulWidget { 107 | @override 108 | _UpsertPageState createState() => _UpsertPageState(); 109 | } 110 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | sqlcool: 27 | path: ../ 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^1.0.0 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | 37 | # For information on the generic Dart part of this file, see the 38 | # following page: https://dart.dev/tools/pub/pubspec 39 | 40 | # The following section is specific to Flutter. 41 | flutter: 42 | # The following line ensures that the Material Icons font is 43 | # included with your application, so that you can use the icons in 44 | # the material Icons class. 45 | uses-material-design: true 46 | # To add assets to your application, add an assets section, like this: 47 | # assets: 48 | # - images/a_dot_burr.jpeg 49 | # - images/a_dot_ham.jpeg 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | # For details regarding adding assets from package dependencies, see 53 | # https://flutter.dev/assets-and-images/#from-packages 54 | # To add custom fonts to your application, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts from package dependencies, 72 | # see https://flutter.dev/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/sqlcool/f548de2187d190476d712bfaa208966f9a40733b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sqlcool 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/sqlcool.dart: -------------------------------------------------------------------------------- 1 | /// A library to use Sqflite with ease 2 | library sqlcool; 3 | 4 | export "src/bloc_select.dart"; 5 | export 'src/bloc_select_rows.dart'; 6 | export "src/database.dart"; 7 | export 'src/exceptions.dart'; 8 | export 'src/models.dart'; 9 | export 'src/safe_api.dart'; 10 | export 'src/schema/models/column.dart'; 11 | export 'src/schema/models/dbmodels.dart'; 12 | export 'src/schema/models/row.dart'; 13 | export 'src/schema/models/schema.dart'; 14 | export 'src/schema/models/table.dart'; 15 | -------------------------------------------------------------------------------- /lib/src/bloc_select.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | import 'database.dart'; 6 | 7 | /// A ready to use select bloc 8 | /// 9 | /// Provides a stream with the rows corresponding to the query. This 10 | /// stream, accssible via [items], will send new data if something changes 11 | /// in the database if the [reactive] parameter is true 12 | /// 13 | /// Join queries are possible. 14 | /// 15 | /// Important: you must provide either a [table] or a [query] argument 16 | class SelectBloc { 17 | /// Create a select bloc with the specified options. The select 18 | /// bloc will fire a query on creation 19 | SelectBloc( 20 | {@required this.database, 21 | this.query, 22 | this.table, 23 | this.offset, 24 | this.limit, 25 | this.where, 26 | this.columns = "*", 27 | this.joinTable, 28 | this.joinOn, 29 | this.orderBy, 30 | this.reactive = false, 31 | this.verbose = false}) 32 | : assert(database != null), 33 | assert(database.isReady) { 34 | if ((query == null) && (table == null)) { 35 | throw ArgumentError("Please provide either a table or a query argument"); 36 | } 37 | 38 | _getItems(); 39 | if (reactive) { 40 | _changefeed = database.changefeed.listen((change) { 41 | if ((table != null && change.table == table) || 42 | (query != null && change.query == query)) { 43 | _getItems(); 44 | } 45 | if (verbose) { 46 | print("CHANGE IN THE DATABASE: $change"); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | /// The database 53 | final Db database; 54 | 55 | /// The table name 56 | final String table; 57 | 58 | /// The query, which if used, will ignore all else statements 59 | final String query; 60 | 61 | /// Offset sql statement 62 | int offset; 63 | 64 | /// Limit sql statement 65 | int limit; 66 | 67 | /// Order by sql statement 68 | String orderBy; 69 | 70 | /// Select sql statement 71 | String columns; 72 | 73 | /// Where sql statement 74 | String where; 75 | 76 | /// The join sql statement 77 | String joinTable; 78 | 79 | /// The on sql statement 80 | String joinOn; 81 | 82 | /// The reactivity of the bloc. Will send new values in [items] 83 | /// when something changes in the database if set to true 84 | bool reactive; 85 | 86 | /// The verbosity 87 | bool verbose; 88 | 89 | StreamSubscription _changefeed; 90 | final _itemController = 91 | StreamController>>.broadcast(); 92 | bool _changefeedIsActive = true; 93 | 94 | /// A stream of rows returned by the query. Will return new items 95 | /// if something changes in the database when the [reactive] parameter 96 | /// is true 97 | Stream>> get items => _itemController.stream; 98 | 99 | /// A convenience method to update the bloc items if needed 100 | /// by adding to the sink 101 | void update(List> _items) { 102 | _itemController.sink.add(_items); 103 | } 104 | 105 | /// Cancel the changefeed subscription 106 | void dispose() { 107 | _itemController.close(); 108 | if (reactive) { 109 | _changefeed.cancel(); 110 | _changefeedIsActive = false; 111 | } 112 | } 113 | 114 | //A way to externally trigger the BLOC to rerun the query. 115 | //Very useful if you have a changing filter being used as part of the 116 | //search query. 117 | Future reload() async { 118 | return _getItems(); 119 | } 120 | 121 | Future _getItems() async { 122 | List> res; 123 | 124 | try { 125 | if (query != null) { 126 | res = await database.query(query, verbose: verbose); 127 | } else if (joinTable != null) { 128 | res = await database.join( 129 | table: table, 130 | columns: columns, 131 | joinTable: joinTable, 132 | joinOn: joinOn, 133 | offset: offset, 134 | limit: limit, 135 | where: where, 136 | orderBy: orderBy, 137 | verbose: verbose); 138 | } else { 139 | res = await database.select( 140 | table: table, 141 | columns: columns, 142 | offset: offset, 143 | limit: limit, 144 | where: where, 145 | orderBy: orderBy, 146 | verbose: verbose); 147 | } 148 | } catch (e) { 149 | if (_changefeedIsActive) { 150 | _itemController.sink.addError(e); 151 | } else { 152 | rethrow; 153 | } 154 | return; 155 | } 156 | 157 | if (_changefeedIsActive) { 158 | _itemController.sink.add(res); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /lib/src/bloc_select_rows.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | import 'safe_api.dart'; 6 | import 'schema/models/row.dart'; 7 | 8 | /// A ready to use select bloc 9 | /// 10 | /// Provides a stream with the rows corresponding to the query. This 11 | /// stream, accssible via [rows], will send new data if something changes 12 | /// in the database if the [reactive] parameter is true 13 | /// 14 | /// Join queries are possible. 15 | class SqlSelectBloc { 16 | /// Create a select bloc with the specified options. The select 17 | /// bloc will fire a query on creation 18 | SqlSelectBloc( 19 | {@required this.database, 20 | @required this.table, 21 | this.offset, 22 | this.limit, 23 | this.where, 24 | this.columns = "*", 25 | this.joinTable, 26 | this.joinOn, 27 | this.orderBy, 28 | this.reactive = false, 29 | this.verbose = false}) 30 | : assert(database != null), 31 | assert(database.isReady) { 32 | _getItems(); 33 | if (reactive) { 34 | _changefeed = database.changefeed.listen((change) { 35 | if (table != null && change.table == table) { 36 | _getItems(); 37 | } 38 | if (verbose) { 39 | print("CHANGE IN THE DATABASE: $change"); 40 | } 41 | }); 42 | } 43 | } 44 | 45 | /// The database 46 | final SqlDb database; 47 | 48 | /// The table name 49 | final String table; 50 | 51 | /// Offset sql statement 52 | int offset; 53 | 54 | /// Limit sql statement 55 | int limit; 56 | 57 | /// Order by sql statement 58 | String orderBy; 59 | 60 | /// Select sql statement 61 | String columns; 62 | 63 | /// Where sql statement 64 | String where; 65 | 66 | /// The join sql statement 67 | String joinTable; 68 | 69 | /// The on sql statement 70 | String joinOn; 71 | 72 | /// The reactivity of the bloc. Will send new values in [items] 73 | /// when something changes in the database if set to true 74 | bool reactive; 75 | 76 | /// The verbosity 77 | bool verbose; 78 | 79 | StreamSubscription _changefeed; 80 | final _rowsController = StreamController>.broadcast(); 81 | bool _changefeedIsActive = true; 82 | 83 | /// A stream of rows returned by the query. Will return new items 84 | /// if something changes in the database when the [reactive] parameter 85 | /// is true 86 | Stream> get rows => _rowsController.stream; 87 | 88 | /// A convenience method to update the bloc items if needed 89 | /// by adding to the sink 90 | void update(List _rows) => _rowsController.sink.add(_rows); 91 | 92 | /// Cancel the changefeed subscription 93 | void dispose() { 94 | _rowsController.close(); 95 | if (reactive) { 96 | _changefeed.cancel(); 97 | _changefeedIsActive = false; 98 | } 99 | } 100 | 101 | Future _getItems() async { 102 | final rows = []; 103 | try { 104 | if (joinTable != null) { 105 | rows.addAll(await database.join( 106 | table: table, 107 | columns: columns, 108 | joinTable: joinTable, 109 | joinOn: joinOn, 110 | offset: offset, 111 | limit: limit, 112 | where: where, 113 | orderBy: orderBy, 114 | verbose: verbose)); 115 | } else { 116 | rows.addAll(await database.select( 117 | table: table, 118 | columns: columns, 119 | offset: offset, 120 | limit: limit, 121 | where: where, 122 | orderBy: orderBy, 123 | verbose: verbose)); 124 | } 125 | } catch (e) { 126 | if (_changefeedIsActive) { 127 | _rowsController.sink.addError(e); 128 | } else { 129 | rethrow; 130 | } 131 | return; 132 | } 133 | if (_changefeedIsActive) { 134 | _rowsController.sink.add(rows); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/src/exceptions.dart: -------------------------------------------------------------------------------- 1 | /// An exception for when the database is not ready 2 | class DatabaseNotReady implements Exception { 3 | /// A message can be passed or the default message will be used 4 | DatabaseNotReady([this.message]) { 5 | message ??= _msg; 6 | } 7 | 8 | /// The error message 9 | String message; 10 | 11 | final String _msg = 12 | "The Sqlcool database is not ready. This happens when a query is " 13 | "fired and the database has not finished initializing."; 14 | } 15 | 16 | /// An exception for an error on the onDelete constraint in a foreigh key 17 | /// for a table schema 18 | class OnDeleteConstraintUnknown implements Exception { 19 | /// Provide a message 20 | OnDeleteConstraintUnknown(this.message); 21 | 22 | /// The error message 23 | final String message; 24 | } 25 | 26 | /// An exception for reading or writing a database asset 27 | class DatabaseAssetProblem implements Exception { 28 | /// Provide a message 29 | DatabaseAssetProblem(this.message); 30 | 31 | /// The error message 32 | final String message; 33 | } 34 | 35 | /// An exception for a read query 36 | class ReadQueryException implements Exception { 37 | /// Provide a message 38 | ReadQueryException(this.message); 39 | 40 | /// The error message 41 | final String message; 42 | } 43 | 44 | /// An exception for a write query 45 | class WriteQueryException implements Exception { 46 | /// Provide a message 47 | WriteQueryException(this.message); 48 | 49 | /// The error message 50 | final String message; 51 | } 52 | 53 | /// An exception for a raw query 54 | class RawQueryException implements Exception { 55 | /// Provide a message 56 | RawQueryException(this.message); 57 | 58 | /// The error message 59 | final String message; 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/models.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | /// Types of database changes 4 | enum DatabaseChange { 5 | /// An insert operation in the database 6 | insert, 7 | 8 | /// An update operation in the database 9 | update, 10 | 11 | /// A delete operation in the database 12 | delete, 13 | 14 | /// An upsert operation in the database 15 | upsert 16 | } 17 | 18 | /// A database change event. Used by the changefeed 19 | class DatabaseChangeEvent { 20 | /// Default database change event 21 | DatabaseChangeEvent({ 22 | @required this.type, 23 | @required this.value, 24 | @required this.query, 25 | @required this.table, 26 | @required this.executionTime, 27 | this.data, 28 | }); 29 | 30 | /// Type of the change 31 | final DatabaseChange type; 32 | 33 | /// Change value: number of items affected 34 | final int value; 35 | 36 | /// The query that made the changes 37 | final String query; 38 | 39 | /// The query execution time 40 | final num executionTime; 41 | 42 | /// The table where the changes occur 43 | final String table; 44 | 45 | /// The data manipulated by the query 46 | final Map data; 47 | 48 | /// Human readable format 49 | @override 50 | String toString() { 51 | var s = ""; 52 | if (value > 1) { 53 | s = "s"; 54 | } 55 | var msg = ""; 56 | if (type == DatabaseChange.delete) { 57 | msg += "$value item$s deleted"; 58 | } else if (type == DatabaseChange.update) { 59 | msg += "$value item$s updated"; 60 | } else if (type == DatabaseChange.insert) { 61 | msg += "$value item$s inserted"; 62 | } 63 | msg += "\n$type : $value"; 64 | msg += "\n$query in $executionTime ms"; 65 | return msg; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/safe_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | 6 | import 'database.dart'; 7 | import 'models.dart'; 8 | import 'schema/models/row.dart'; 9 | import 'schema/models/schema.dart'; 10 | import 'schema/models/table.dart'; 11 | 12 | /// A Sqlite database 13 | class SqlDb { 14 | /// Base constructor 15 | SqlDb() { 16 | _db = Db(sqfliteDatabase: sqfliteDatabase); 17 | } 18 | 19 | /// An Sqlite database: 20 | /// 21 | /// Use this parameter if you want to work with an existing 22 | /// Sqflite database 23 | Database sqfliteDatabase; 24 | 25 | Db _db; 26 | 27 | /// A stream of [DatabaseChangeEvent] with all the changes 28 | /// that occur in the database 29 | Stream get changefeed => _db.changefeed; 30 | 31 | /// This Sqflite [Database] 32 | Database get database => _db.database; 33 | 34 | /// This Sqlite file 35 | File get file => _db.file; 36 | 37 | /// Check the existence of a schema 38 | bool get hasSchema => _db.hasSchema; 39 | 40 | /// This database state 41 | bool get isReady => _db.isReady; 42 | 43 | /// The on ready callback: fired when the database 44 | /// is ready to operate 45 | Future get onReady => _db.onReady; 46 | 47 | /// This database schema 48 | DbSchema get schema => _db.schema; 49 | 50 | /// Initialize the database 51 | /// 52 | /// The database can be initialized either from an asset file 53 | /// with the [fromAsset] parameter or from a [schema] or from 54 | /// create table and other [queries]. A [schema] must be provided. 55 | Future init( 56 | {@required String path, 57 | @required List schema, 58 | bool absolutePath = false, 59 | List queries = const [], 60 | bool verbose = false, 61 | String fromAsset, 62 | bool debug = false}) async { 63 | assert(schema != null); 64 | await _db.init( 65 | path: path, 66 | absolutePath: absolutePath, 67 | queries: queries, 68 | schema: schema, 69 | verbose: verbose, 70 | fromAsset: fromAsset, 71 | debug: debug); 72 | } 73 | 74 | /// Insert a row in a table 75 | /// 76 | /// [table] the table to insert into 77 | /// 78 | /// Returns a future with the last inserted id 79 | Future insert( 80 | {@required String table, 81 | @required DbRow row, 82 | bool verbose = false}) async { 83 | int res; 84 | try { 85 | res = await _db.insert( 86 | table: table, row: row.toStringsMap(), verbose: verbose); 87 | } catch (e) { 88 | rethrow; 89 | } 90 | return res; 91 | } 92 | 93 | /// Insert a row in a table with conflict algorithm 94 | /// 95 | /// [table] the table to insert into. [row] is a map of the data 96 | /// to insert 97 | /// 98 | /// Returns a future with the last inserted id 99 | Future insertManageConflict( 100 | {@required String table, 101 | @required ConflictAlgorithm conflictAlgorithm, 102 | @required DbRow row, 103 | bool verbose = false}) async { 104 | int res; 105 | try { 106 | res = await _db.insertManageConflict( 107 | table: table, 108 | conflictAlgorithm: conflictAlgorithm, 109 | row: row.toMap(), 110 | verbose: verbose); 111 | } catch (e) { 112 | rethrow; 113 | } 114 | return res; 115 | } 116 | 117 | /// A select query 118 | Future> select( 119 | {@required String table, 120 | String columns = "*", 121 | String where, 122 | String orderBy, 123 | int limit, 124 | int offset, 125 | String groupBy, 126 | bool verbose = false}) async { 127 | List> data; 128 | try { 129 | data = await _db.select( 130 | table: table, 131 | columns: columns, 132 | where: where, 133 | orderBy: orderBy, 134 | limit: limit, 135 | offset: offset, 136 | groupBy: groupBy, 137 | verbose: verbose); 138 | } catch (e) { 139 | rethrow; 140 | } 141 | return _rowsFromRawData(table, data); 142 | } 143 | 144 | /// Update some datapoints in the database 145 | Future update( 146 | {@required String table, 147 | @required DbRow row, 148 | @required String where, 149 | bool verbose = false}) async { 150 | int res; 151 | try { 152 | res = await _db.update( 153 | table: table, 154 | row: row.toStringsMap(), 155 | where: where, 156 | verbose: verbose); 157 | } catch (e) { 158 | rethrow; 159 | } 160 | return res; 161 | } 162 | 163 | /// Insert a row if it does not exist or update it 164 | /// 165 | /// It is highly recommended to use an unique index for the table 166 | /// to upsert into 167 | Future upsert( 168 | {@required String table, 169 | @required DbRow row, 170 | //@required List columns, 171 | List preserveColumns = const [], 172 | String indexColumn, 173 | bool verbose = false}) async { 174 | try { 175 | await _db.upsert(table: table, row: row.toStringsMap(), verbose: verbose); 176 | } catch (e) { 177 | rethrow; 178 | } 179 | } 180 | 181 | /// Insert rows in a table 182 | Future> batchInsert( 183 | {@required String table, 184 | @required List rows, 185 | ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.rollback, 186 | bool verbose = false}) async { 187 | final data = >[]; 188 | rows.forEach((row) => data.add(row.toStringsMap())); 189 | var res = []; 190 | try { 191 | res = await _db.batchInsert(table: table, rows: data); 192 | } catch (e) { 193 | rethrow; 194 | } 195 | return res; 196 | } 197 | 198 | /// A select query with a join 199 | Future> join( 200 | {@required String table, 201 | @required String joinTable, 202 | @required String joinOn, 203 | String columns = "*", 204 | int offset, 205 | int limit, 206 | String orderBy, 207 | String where, 208 | String groupBy, 209 | bool verbose = false}) async { 210 | List> data; 211 | try { 212 | data = await _db.join( 213 | table: table, 214 | joinTable: joinTable, 215 | joinOn: joinOn, 216 | columns: columns, 217 | offset: offset, 218 | limit: limit, 219 | orderBy: orderBy, 220 | where: where, 221 | groupBy: groupBy, 222 | verbose: verbose); 223 | } catch (e) { 224 | rethrow; 225 | } 226 | return _rowsFromRawData(table, data); 227 | } 228 | 229 | /// A select query with a join on multiple tables 230 | Future> mJoin( 231 | {@required String table, 232 | @required List joinsTables, 233 | @required List joinsOn, 234 | String columns = "*", 235 | int offset, 236 | int limit, 237 | String orderBy, 238 | String where, 239 | String groupBy, 240 | bool verbose = false}) async { 241 | List> data; 242 | try { 243 | data = await _db.mJoin( 244 | table: table, 245 | joinsTables: joinsTables, 246 | joinsOn: joinsOn, 247 | columns: columns, 248 | offset: offset, 249 | limit: limit, 250 | orderBy: orderBy, 251 | where: where, 252 | groupBy: groupBy, 253 | verbose: verbose); 254 | } catch (e) { 255 | rethrow; 256 | } 257 | return _rowsFromRawData(table, data); 258 | } 259 | 260 | /// Delete some datapoints from the database 261 | Future delete( 262 | {@required String table, 263 | @required String where, 264 | bool verbose = false}) async => 265 | _db.delete(table: table, where: where, verbose: verbose); 266 | 267 | /// Execute a query 268 | Future>> query(String q, 269 | {bool verbose = false}) async => 270 | _db.query(q, verbose: verbose); 271 | 272 | /// count rows in a table 273 | Future count( 274 | {@required String table, 275 | String where, 276 | String columns = "id", 277 | bool verbose = false}) async => 278 | _db.count(table: table, where: where, columns: columns, verbose: verbose); 279 | 280 | List _rowsFromRawData(String table, List> data) { 281 | final rows = []; 282 | DbTable t; 283 | try { 284 | t = schema.table(table); 285 | } catch (e) { 286 | rethrow; 287 | } 288 | data.forEach((r) => rows.add(DbRow.fromMap(t, r))); 289 | return rows; 290 | } 291 | 292 | /// Check if a value exists in the table 293 | Future exists( 294 | {@required String table, 295 | @required String where, 296 | bool verbose = false}) async => 297 | _db.exists(table: table, where: where, verbose: verbose); 298 | 299 | /// Dispose when finished using 300 | void dispose() => _db.dispose(); 301 | } 302 | -------------------------------------------------------------------------------- /lib/src/schema/models/column.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | 4 | import '../../exceptions.dart'; 5 | 6 | /// Convert an on delete constraint to a string 7 | String onDeleteToString(OnDelete onDelete) { 8 | String res; 9 | switch (onDelete) { 10 | case OnDelete.cascade: 11 | res = "cascade"; 12 | break; 13 | case OnDelete.restrict: 14 | res = "restrict"; 15 | break; 16 | case OnDelete.setDefault: 17 | res = "set_default"; 18 | break; 19 | case OnDelete.setNull: 20 | res = "set_null"; 21 | break; 22 | } 23 | return res; 24 | } 25 | 26 | /// Convert a string to an on delete constraint 27 | OnDelete stringToOnDelete(String value) { 28 | OnDelete res; 29 | switch (value) { 30 | case "restrict": 31 | res = OnDelete.restrict; 32 | break; 33 | case "cascade": 34 | res = OnDelete.cascade; 35 | break; 36 | case "set_null": 37 | res = OnDelete.setNull; 38 | break; 39 | case "set_default": 40 | res = OnDelete.setDefault; 41 | break; 42 | default: 43 | throw OnDeleteConstraintUnknown("Unknown on delete constraint $value"); 44 | } 45 | return res; 46 | } 47 | 48 | /// A database column representation 49 | @immutable 50 | class DbColumn { 51 | /// Provide a name and a type 52 | const DbColumn( 53 | {@required this.name, 54 | @required this.type, 55 | this.unique = false, 56 | this.nullable = false, 57 | this.check, 58 | this.defaultValue, 59 | this.isForeignKey = false, 60 | this.reference, 61 | this.onDelete}); 62 | 63 | /// The column name 64 | final String name; 65 | 66 | /// The data type of the column 67 | final DbColumnType type; 68 | 69 | /// Is the column unique 70 | final bool unique; 71 | 72 | /// Is the column nullable 73 | final bool nullable; 74 | 75 | /// The column-s default value 76 | final String defaultValue; 77 | 78 | /// A check constraint 79 | final String check; 80 | 81 | /// If the column is a foreign key 82 | final bool isForeignKey; 83 | 84 | /// A foreign key table name reference 85 | final String reference; 86 | 87 | /// The on delete constraint on a foreign key 88 | final OnDelete onDelete; 89 | 90 | /// print a description of the schema 91 | String describe({String spacer = "", bool isPrint = true}) { 92 | final lines = [ 93 | "${spacer}Column $name:", 94 | "$spacer - Type: $type", 95 | "$spacer - Unique: $unique", 96 | "$spacer - Nullable: $nullable", 97 | "$spacer - Default value: $defaultValue", 98 | "$spacer - Is foreign key: $isForeignKey", 99 | "$spacer - Reference: $reference", 100 | "$spacer - On delete: $onDelete", 101 | ]; 102 | var s = ""; 103 | switch (isPrint) { 104 | case false: 105 | s = lines.join("\n"); 106 | break; 107 | default: 108 | print(lines.join("\n")); 109 | } 110 | return s; 111 | } 112 | 113 | /// convert a column type to a string 114 | String typeToString() { 115 | String res; 116 | switch (type) { 117 | case DbColumnType.varchar: 118 | res = "varchar"; 119 | break; 120 | case DbColumnType.integer: 121 | res = "integer"; 122 | break; 123 | case DbColumnType.real: 124 | res = "real"; 125 | break; 126 | case DbColumnType.boolean: 127 | res = "boolean"; 128 | break; 129 | case DbColumnType.text: 130 | res = "text"; 131 | break; 132 | case DbColumnType.timestamp: 133 | res = "timestamp"; 134 | break; 135 | case DbColumnType.blob: 136 | res = "blob"; 137 | break; 138 | } 139 | return res; 140 | } 141 | 142 | @override 143 | String toString() => "$name $type"; 144 | } 145 | 146 | /// The type of a database column 147 | enum DbColumnType { 148 | /// A varchar column 149 | varchar, 150 | 151 | /// An integer column 152 | integer, 153 | 154 | /// A double column 155 | real, 156 | 157 | /// A text column 158 | text, 159 | 160 | /// A boolean colum 161 | boolean, 162 | 163 | /// A blob value 164 | blob, 165 | 166 | /// An automatic timestamp 167 | timestamp 168 | } 169 | -------------------------------------------------------------------------------- /lib/src/schema/models/dbmodels.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sqlcool/src/exceptions.dart'; 3 | 4 | import '../../database.dart'; 5 | import 'column.dart'; 6 | import 'table.dart'; 7 | 8 | /// The database model class to extend 9 | class DbModel { 10 | /// The database id of the model instance 11 | /// 12 | /// **Important** : it must be overriden 13 | int id; 14 | 15 | /// get the database 16 | Db get db => null; 17 | 18 | /// get the table schema 19 | DbTable get table => null; 20 | 21 | /// The database row serializer for the model 22 | /// 23 | /// **Important** : it must be overriden 24 | Map toDb() => {}; 25 | 26 | /// The database row deserializer for the model 27 | /// 28 | /// **Important** : it must be overriden 29 | DbModel fromDb(Map map) => null; 30 | 31 | /// Select rows in the database table with joins on foreign keys 32 | Future> sqlJoin( 33 | {int offset, 34 | int limit, 35 | String orderBy, 36 | String where, 37 | String groupBy, 38 | bool verbose = false}) async { 39 | _checkDbIsReady(); 40 | print("> Sqljoin for table $table"); 41 | //table.describe(); 42 | final _joinTables = []; 43 | final _joinOn = []; 44 | final _select = []; 45 | final _encodedFks = <_EncodedFk>[]; 46 | if (!table.hasColumn("id")) { 47 | table.columns.add(const DbColumn(name: "id", type: DbColumnType.integer)); 48 | } 49 | table.columns.forEach((c) { 50 | if (!c.isForeignKey) { 51 | _select.add("${table.name}.${c.name} AS ${c.name}"); 52 | } 53 | }); 54 | for (final fkCol in table.foreignKeys) { 55 | final fkTable = db.schema.table(fkCol.reference); 56 | _joinTables.add(fkTable.name); 57 | //print("FK COLS $fkTable: ${fkTable.columns}"); 58 | final c = fkTable.columns; 59 | if (!fkTable.hasColumn("id")) { 60 | c.add(const DbColumn(name: "id", type: DbColumnType.integer)); 61 | } 62 | //print("NEW FK COLS ${fkTable.columns}"); 63 | _joinOn.add("${table.name}.${fkCol.name}=${fkTable.name}.id"); 64 | //print("Joins add ${table.name}.${fkCol.name}=${fkTable.name}.id"); 65 | for (final _fkTableCol in c) { 66 | final encodedName = "${fkTable.name}_${_fkTableCol.name}"; 67 | final fk = _EncodedFk( 68 | table: fkTable, 69 | name: _fkTableCol.name, 70 | encodedName: encodedName, 71 | refColName: fkCol.name); 72 | _encodedFks.add(fk); 73 | final encodedFkName = "${fkTable.name}.${fk.name} AS $encodedName"; 74 | _select.add(encodedFkName); 75 | } 76 | } 77 | final columns = _select.join(","); 78 | final res = await db.mJoin( 79 | table: table.name, 80 | joinsTables: _joinTables, 81 | joinsOn: _joinOn, 82 | columns: columns, 83 | offset: offset, 84 | limit: limit, 85 | where: where, 86 | groupBy: groupBy, 87 | verbose: verbose); 88 | final endRes = >[]; 89 | //print("\nRES $res\n"); 90 | for (final row in res) { 91 | final endRow = {}; 92 | final fkData = >{}; 93 | row.forEach((key, dynamic value) { 94 | final encodedFk = 95 | _encodedFks.where((element) => element.encodedName == key).toList(); 96 | if (encodedFk.isEmpty) { 97 | // it is not a foreign key 98 | endRow[key] = value; 99 | } else { 100 | final efk = encodedFk[0]; 101 | //print("EFK $efk"); 102 | if (!fkData.containsKey(efk.refColName)) { 103 | fkData[efk.refColName] = {}; 104 | } 105 | fkData[efk.refColName][efk.name] = value; 106 | //endRow[key][encodedFk[0].refColName] = value; 107 | //print("FKDATA : $fkData"); 108 | } 109 | }); 110 | for (final c in fkData.keys) { 111 | //print("FK DATA $c : ${fkData[c]}"); 112 | endRow[c] = fkData[c]; 113 | } 114 | //print("END ROW $endRow"); 115 | endRes.add(endRow); 116 | } 117 | //print("QUERY END RES: $endRes"); 118 | final endModelData = []; 119 | for (final r in endRes) { 120 | endModelData.add(fromDb(r)); 121 | } 122 | //print("End model data: $endModelData"); 123 | return endModelData; 124 | } 125 | 126 | /// Select rows in the database table 127 | Future> sqlSelect( 128 | {String where, 129 | String orderBy, 130 | int limit, 131 | int offset, 132 | String groupBy, 133 | bool verbose = false}) async { 134 | _checkDbIsReady(); 135 | // do not take the foreign keys 136 | final cols = ["id"]; 137 | for (final col in table.columns) { 138 | if (!col.isForeignKey) { 139 | cols.add(col.name); 140 | } 141 | } 142 | final columns = cols.join(","); 143 | final res = await db.select( 144 | table: table.name, 145 | columns: columns, 146 | where: where, 147 | orderBy: orderBy, 148 | limit: limit, 149 | offset: offset, 150 | groupBy: groupBy, 151 | verbose: verbose); 152 | final endRes = []; 153 | for (final row in res) { 154 | endRes.add(fromDb(row)); 155 | } 156 | return endRes; 157 | } 158 | 159 | /// Update a row in the database table 160 | Future sqlUpdate({bool verbose = false}) async { 161 | _checkDbIsReady(); 162 | final data = this.toDb(); 163 | final row = _toStringsMap(data); 164 | await db 165 | .update(table: table.name, row: row, where: 'id=$id', verbose: verbose) 166 | .catchError((dynamic e) => 167 | throw WriteQueryException("Can not update model into database $e")); 168 | } 169 | 170 | /// Upsert a row in the database table 171 | Future sqlUpsert( 172 | {bool verbose = false, 173 | String indexColumn, 174 | List preserveColumns = const []}) async { 175 | _checkDbIsReady(); 176 | final data = this.toDb(); 177 | final row = _toStringsMap(data); 178 | await db 179 | .upsert( 180 | table: table.name, 181 | row: row, 182 | indexColumn: indexColumn, 183 | preserveColumns: preserveColumns, 184 | verbose: verbose) 185 | .catchError((dynamic e) => 186 | throw WriteQueryException("Can not upsert model into database $e")); 187 | } 188 | 189 | /// Insert a row in the database table 190 | Future sqlInsert({bool verbose = false}) async { 191 | _checkDbIsReady(); 192 | final data = this.toDb(); 193 | final row = _toStringsMap(data); 194 | final id = await db 195 | .insert(table: table.name, row: row, verbose: verbose) 196 | .catchError((dynamic e) => 197 | throw WriteQueryException("Can not insert model into database $e")); 198 | return id; 199 | } 200 | 201 | /// Insert a row in the database table if it does not exist already 202 | @Deprecated( 203 | "The insertIfNotExists function will be removed after version 4.4.0") 204 | Future sqlInsertIfNotExists({bool verbose = false}) async { 205 | _checkDbIsReady(); 206 | final data = this.toDb(); 207 | final row = _toStringsMap(data); 208 | final id = await db 209 | .insertIfNotExists(table: table.name, row: row, verbose: verbose) 210 | .catchError((dynamic e) => 211 | throw WriteQueryException("Can not insert model into database $e")); 212 | return id; 213 | } 214 | 215 | /// Delete an instance from the database 216 | Future sqlDelete({String where, bool verbose = false}) async { 217 | _checkDbIsReady(); 218 | var _where = where; 219 | if (where == null) { 220 | assert(id != null, 221 | "The instance id must not be null if no where clause is used"); 222 | _where = "id=$id"; 223 | } 224 | await db 225 | .delete(table: table.name, where: _where, verbose: verbose) 226 | .catchError((dynamic e) => 227 | throw WriteQueryException("Can not delete model from database $e")); 228 | } 229 | 230 | /// Count rows 231 | Future sqlCount({String where, bool verbose = false}) async { 232 | final n = db 233 | .count(table: table.name, where: where, verbose: verbose) 234 | .catchError((dynamic e) => 235 | throw ReadQueryException("Can not count from database $e")); 236 | return n; 237 | } 238 | 239 | Map _toStringsMap(Map map) { 240 | final res = {}; 241 | map.forEach((String k, dynamic v) { 242 | if (v == null) { 243 | res[k] = null; 244 | } else { 245 | res[k] = "$v"; 246 | } 247 | }); 248 | return res; 249 | } 250 | 251 | void _checkDbIsReady() { 252 | assert(table != null); 253 | assert(db != null); 254 | assert(db.isReady, "Please initialize the database by running db.init()"); 255 | } 256 | } 257 | 258 | class _EncodedFk { 259 | const _EncodedFk( 260 | {@required this.table, 261 | @required this.name, 262 | @required this.encodedName, 263 | @required this.refColName}); 264 | 265 | final DbTable table; 266 | final String name; 267 | final String encodedName; 268 | final String refColName; 269 | 270 | @override 271 | String toString() { 272 | final s = StringBuffer() 273 | ..write("Encoded fk:") 274 | ..write("\n- Table: $table") 275 | ..write("\n- Name: $name") 276 | ..write("\n- Encoded name: $encodedName") 277 | ..write("\n- Ref col name: $refColName"); 278 | return s.toString(); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /lib/src/schema/models/row.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:sqlcool/sqlcool.dart'; 5 | 6 | import 'column.dart'; 7 | import 'table.dart'; 8 | 9 | /// A database record 10 | @immutable 11 | class DbRecord { 12 | /// Default constructor 13 | DbRecord(this.key, this.value) { 14 | if (!(value is T)) { 15 | throw ArgumentError("Provide a value of type $T for record $key"); 16 | } 17 | } 18 | 19 | /// The record column name 20 | final String key; 21 | 22 | /// The record value 23 | final T value; 24 | 25 | /// Get the type 26 | Type get type => T; 27 | 28 | /// Get a copy of a record with a specific type 29 | /// 30 | /// Accepted types: String, int, double, bool 31 | /// and Uint8List 32 | DbRecord copyWithType(Type t) { 33 | switch (t) { 34 | case String: 35 | return DbRecord(key, value.toString()); 36 | break; 37 | case int: 38 | try { 39 | final v = int.parse(value.toString()); 40 | return DbRecord(key, v); 41 | } catch (e) { 42 | throw Exception("Can not convert $value to $t " 43 | "for key $key"); 44 | } 45 | break; 46 | case double: 47 | try { 48 | final v = double.parse(value.toString()); 49 | return DbRecord(key, v); 50 | } catch (e) { 51 | throw Exception("Can not convert $value to $t " 52 | "for key $key"); 53 | } 54 | break; 55 | case bool: 56 | final dynamic val = value.toString(); 57 | if (val == "true") { 58 | return DbRecord(key, true); 59 | } else if (val == "false") { 60 | return DbRecord(key, false); 61 | } else { 62 | throw Exception("Can not convert $value to $t " 63 | "for key $key"); 64 | } 65 | break; 66 | case Uint8List: 67 | try { 68 | final v = value as Uint8List; 69 | return DbRecord(key, v); 70 | } catch (e) { 71 | throw Exception("Can not convert $value to $t " 72 | "for key $key"); 73 | } 74 | break; 75 | } 76 | return null; 77 | } 78 | 79 | @override 80 | String toString() { 81 | return "$key : $value <$type>"; 82 | } 83 | } 84 | 85 | /// A database row 86 | @immutable 87 | class DbRow { 88 | /// Default constructor 89 | const DbRow(this.records); 90 | 91 | /// Build a row from a single record 92 | factory DbRow.fromRecord(DbRecord record) => DbRow([record]); 93 | 94 | /// Create from a map of strings 95 | factory DbRow.fromMap(DbTable table, Map row) { 96 | final recs = []; 97 | row.forEach((key, dynamic value) { 98 | if (key == "id") { 99 | recs.add(DbRecord(key, value as int)); 100 | } else { 101 | final col = table.column(key); 102 | if (col == null) { 103 | recs.add(DbRecord(key, value)); 104 | } else { 105 | //print("COL ${col.name} ${col.type}"); 106 | switch (col.type) { 107 | case DbColumnType.varchar: 108 | recs.add(DbRecord(key, value.toString())); 109 | break; 110 | case DbColumnType.text: 111 | recs.add(DbRecord(key, value.toString())); 112 | break; 113 | case DbColumnType.integer: 114 | int v; 115 | try { 116 | v = value as int; 117 | } catch (e) { 118 | rethrow; 119 | } 120 | recs.add(DbRecord(key, v)); 121 | break; 122 | case DbColumnType.real: 123 | double v; 124 | try { 125 | v = value as double; 126 | } catch (e) { 127 | rethrow; 128 | } 129 | recs.add(DbRecord(key, v)); 130 | break; 131 | case DbColumnType.boolean: 132 | bool v; 133 | if (value == "false") { 134 | v = false; 135 | } else if (value == "true") { 136 | v = true; 137 | } else { 138 | throw Exception("Wrong value $value for boolean field $key"); 139 | } 140 | recs.add(DbRecord(key, v)); 141 | break; 142 | case DbColumnType.timestamp: 143 | int v; 144 | try { 145 | v = value as int; 146 | } catch (e) { 147 | rethrow; 148 | } 149 | recs.add(DbRecord(key, v)); 150 | break; 151 | case DbColumnType.blob: 152 | Uint8List v; 153 | try { 154 | v = value as Uint8List; 155 | } catch (e) { 156 | rethrow; 157 | } 158 | recs.add(DbRecord(key, v)); 159 | break; 160 | } 161 | } 162 | } 163 | }); 164 | //print("ROW $recs"); 165 | return DbRow(recs); 166 | } 167 | 168 | /// The row's records 169 | final List records; 170 | 171 | /// Get a record value 172 | T record(String key) { 173 | final rec = records.firstWhere((r) => r.key == key); 174 | T res; 175 | if (rec != null) { 176 | print("REC $rec"); 177 | try { 178 | if (rec.type != dynamic) { 179 | final r = rec as DbRecord; 180 | res = r.value; 181 | } else { 182 | final r = rec.copyWithType(T) as DbRecord; 183 | res = r.value; 184 | } 185 | } catch (e) { 186 | throw Exception("Type error for record $key : $e"); 187 | } 188 | } 189 | return res; 190 | } 191 | 192 | /// Convert to a map 193 | Map toMap() { 194 | final data = {}; 195 | records.forEach((r) => data["${r.key}"] = r.value); 196 | return data; 197 | } 198 | 199 | /// Convert to a map of strings 200 | Map toStringsMap() { 201 | final data = {}; 202 | records.forEach((r) => data["${r.key}"] = r.value.toString()); 203 | return data; 204 | } 205 | 206 | /// Get a string representation 207 | String line() { 208 | final l = []; 209 | records.forEach((rec) { 210 | l.add("${rec.key} : ${rec.value}"); 211 | }); 212 | return l.join(","); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /lib/src/schema/models/schema.dart: -------------------------------------------------------------------------------- 1 | import 'table.dart'; 2 | 3 | /// The database schema representation 4 | class DbSchema { 5 | /// Provide a set of [DbTable] 6 | DbSchema([this.tables]) { 7 | tables ??= {}; 8 | } 9 | 10 | /// The tables in the database 11 | Set tables; 12 | 13 | /// Get a [DbTable] in the schema from it's name 14 | DbTable table(String name) { 15 | DbTable t; 16 | for (final table in tables) { 17 | if (table.name == name) { 18 | t = table; 19 | break; 20 | } 21 | } 22 | return t; 23 | } 24 | 25 | /// Check if a [DbTable] is present in the schema from it's name 26 | bool hasTable(String name) { 27 | for (final table in tables) { 28 | if (table.name == name) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | /// print a description of the schema 36 | void describe() { 37 | for (final table in tables) { 38 | table.describe(spacer: " "); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sqlcool 2 | description: Easy and reactive Sqlite. Stream of changes, select bloc, custom 3 | database models 4 | homepage: https://github.com/synw/sqlcool 5 | 6 | # The following defines the version and build number for your application. 7 | # A version number is three numbers separated by dots, like 1.2.43 8 | # followed by an optional build number separated by a +. 9 | # Both the version and the builder number may be overridden in flutter 10 | # build by specifying --build-name and --build-number, respectively. 11 | # In Android, build-name is used as versionName while build-number used as versionCode. 12 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 13 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 14 | # Read more about iOS versioning at 15 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 16 | version: 5.1.1 17 | 18 | environment: 19 | sdk: ">=2.7.0 <3.0.0" 20 | 21 | dependencies: 22 | flutter: 23 | sdk: flutter 24 | extra_pedantic: ^1.2.0 25 | path_provider: ^1.6.24 26 | pedantic: ^1.9.2 27 | sqflite: ^1.3.2+1 28 | synchronized: ^2.2.0+2 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | # The following section is specific to Flutter. 37 | flutter: 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | # To add assets to your application, add an assets section, like this: 43 | # assets: 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.dev/assets-and-images/#resolution-aware. 48 | # For details regarding adding assets from package dependencies, see 49 | # https://flutter.dev/assets-and-images/#from-packages 50 | # To add custom fonts to your application, add a fonts section here, 51 | # in this "flutter" section. Each entry in this list should have a 52 | # "family" key with the font family name, and a "fonts" key with a 53 | # list giving the asset and other descriptors for the font. For 54 | # example: 55 | # fonts: 56 | # - family: Schyler 57 | # fonts: 58 | # - asset: fonts/Schyler-Regular.ttf 59 | # - asset: fonts/Schyler-Italic.ttf 60 | # style: italic 61 | # - family: Trajan Pro 62 | # fonts: 63 | # - asset: fonts/TrajanPro.ttf 64 | # - asset: fonts/TrajanPro_Bold.ttf 65 | # weight: 700 66 | # 67 | # For details regarding fonts from package dependencies, 68 | # see https://flutter.dev/custom-fonts/#from-packages 69 | -------------------------------------------------------------------------------- /test/base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | Directory directory; 6 | const MethodChannel channel = MethodChannel('com.tekartik.sqflite'); 7 | final List log = []; 8 | bool setupDone = false; 9 | 10 | Future setup() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | if (setupDone) { 13 | return; 14 | } 15 | directory = await Directory.systemTemp.createTemp(); 16 | 17 | String response; 18 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 19 | //print("METHOD CALL: $methodCall"); 20 | log.add(methodCall); 21 | switch (methodCall.method) { 22 | case "getDatabasesPath": 23 | return directory.path; 24 | break; 25 | case "insert": 26 | return 1; 27 | break; 28 | case "update": 29 | return 1; 30 | break; 31 | case "batch": 32 | return [1, 2]; 33 | break; 34 | case "query": 35 | // count query 36 | if (methodCall.arguments["sql"] == 37 | "SELECT COUNT(id) FROM test WHERE id=1") { 38 | final res = >[ 39 | {"count": 1} 40 | ]; 41 | return res; 42 | } 43 | // exists query 44 | else if (methodCall.arguments["sql"] == 45 | "SELECT COUNT(*) FROM test WHERE id=1") { 46 | final res = >[ 47 | {"count": 1} 48 | ]; 49 | return res; 50 | } // bloc select query 51 | else if (methodCall.arguments["sql"] == "SELECT * FROM test") { 52 | final res = >[ 53 | {"k": "v"}, 54 | {"k": "v"}, 55 | ]; 56 | return res; 57 | } // dbmodels select 58 | else if (methodCall.arguments["sql"] == 59 | "SELECT id,name,price FROM car WHERE price=30000") { 60 | final res = >[ 61 | {"name": "My car", "price": 30000.0}, 62 | ]; 63 | return res; 64 | } // dbmodels other select 65 | else if (methodCall.arguments["sql"] == 66 | 'SELECT id,name,price FROM car WHERE name="My car"') { 67 | final res = >[]; 68 | return res; 69 | } 70 | // dbmodels other select 71 | else if (methodCall.arguments["sql"] == 72 | "SELECT id,name,price FROM car WHERE price=40000") { 73 | final res = >[ 74 | {"name": "My car", "price": 40000.0}, 75 | ]; 76 | return res; 77 | } // dbmodels foreign key 78 | else if (methodCall.arguments["sql"] == 79 | "SELECT car.name AS name,car.price AS price,car.id AS id," 80 | "manufacturer.name AS manufacturer_name,manufacturer.id AS manufacturer_id " 81 | "FROM car INNER JOIN manufacturer ON car.manufacturer=manufacturer.id") { 82 | final res = >[ 83 | { 84 | "id": 1, 85 | "name": "My car", 86 | "price": 10000.0, 87 | "manufacturer_name": "My manufacturer", 88 | "manufacturer_id": 1 89 | }, 90 | ]; 91 | return res; 92 | } else { 93 | final res = >[ 94 | {"k": "v"} 95 | ]; 96 | return res; 97 | } 98 | } 99 | return response; 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /test/changefeed_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | import 'base.dart'; 4 | 5 | Future main() async { 6 | await setup(); 7 | 8 | final db = Db(); 9 | final table1 = DbTable("table1")..varchar("name"); 10 | final table = DbTable("table") 11 | ..varchar("name", unique: true, nullable: true) 12 | ..integer("int", defaultValue: 3, unique: true, check: "int>1") 13 | ..real("real") 14 | ..boolean("bool", defaultValue: true) 15 | ..blob("blob") 16 | ..text("text") 17 | ..index("name") 18 | ..timestamp() 19 | ..foreignKey("table1", unique: false, onDelete: OnDelete.cascade) 20 | ..uniqueTogether("name", "int"); 21 | 22 | await db.init( 23 | path: "test_changefeed_db.sqlite", 24 | schema: [table, table1], 25 | absolutePath: true, 26 | debug: true); 27 | 28 | test("Listen", () async { 29 | db.changefeed.listen((change) { 30 | switch (change.type) { 31 | case DatabaseChange.insert: 32 | expect(change.value, 1); 33 | expect(change.toString().startsWith("""1 item inserted 34 | DatabaseChange.insert : 1 35 | INSERT INTO test (k) VALUES(?) {k: v} in """), true); 36 | break; 37 | case DatabaseChange.delete: 38 | expect(change.value, 1); 39 | break; 40 | case DatabaseChange.update: 41 | expect(change.value, 1); 42 | break; 43 | default: 44 | } 45 | //print("\n\n$change\n\n"); 46 | }); 47 | await db.insert(table: "test", row: {"k": "v"}, verbose: true); 48 | await db.update( 49 | table: "test", row: {"k": "v"}, where: 'name="k"', verbose: true); 50 | await db.delete(table: "test", where: 'name="k"', verbose: true); 51 | db.dispose(); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/db_models_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | 4 | import 'base.dart'; 5 | import 'dbmodels/car.dart'; 6 | import 'dbmodels/conf.dart'; 7 | import 'dbmodels/manufacturer.dart'; 8 | import 'dbmodels/schema.dart'; 9 | 10 | Future main() async { 11 | await setup(); 12 | 13 | await db.init( 14 | path: "testdb_models.sqlite", 15 | schema: [carTable, manufacturerTable], 16 | absolutePath: true); 17 | 18 | tearDown(log.clear); 19 | 20 | test("Init", () async { 21 | final car = Car(name: "My car", price: 10000.0); 22 | assert(car.db == db); 23 | assert(car.table.name == "car"); 24 | }); 25 | 26 | test("Mutations", () async { 27 | // insert 28 | final car = Car(name: "My car", price: 10000.0); 29 | car.id = await car.sqlInsert(); 30 | assert(car.id == 1); 31 | // update 32 | car.price = 30000; 33 | await car.sqlUpdate(verbose: true); 34 | final c = await Car.select(where: "price=30000"); 35 | assert(c.length == 1); 36 | //print("${c[0].price} / ${car.price}"); 37 | assert(c[0].price == car.price); 38 | assert(c[0].name == car.name); 39 | // upsert 40 | car.price = 40000; 41 | await car.sqlUpsert(); 42 | final c2 = await Car.select(where: "price=40000"); 43 | assert(c2.length == 1); 44 | assert(c2[0].price == car.price); 45 | assert(c2[0].name == car.name); 46 | // delete 47 | await car.sqlDelete(); 48 | final c3 = await Car.select(where: 'name="My car"'); 49 | assert(c3.isEmpty); 50 | }); 51 | 52 | test("Foreign keys", () async { 53 | final manufacturer = Manufacturer(name: "My manufacturer"); 54 | manufacturer.id = await manufacturer.sqlInsert(); 55 | final car = Car(name: "My car", price: 10000.0, manufacturer: manufacturer); 56 | car.id = await car.sqlInsert(); 57 | print("Car $car / ${car.manufacturer}"); 58 | final cars = await Car.selectRelated(); 59 | print("Query cars: $cars"); 60 | assert(cars.length == 1); 61 | assert(cars[0].manufacturer.name == "My manufacturer"); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /test/db_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | import 'base.dart'; 4 | 5 | Future main() async { 6 | await setup(); 7 | 8 | final db = Db(); 9 | 10 | tearDown(log.clear); 11 | 12 | group("init", () { 13 | test("Init db", () async { 14 | const schema = """CREATE TABLE item 15 | id INTEGER PRIMARY KEY, 16 | name TEXT NOT NULL 17 | )"""; 18 | const q = [ 19 | schema, 20 | 'INSERT INTO item(name) values("Name 1")', 21 | 'INSERT INTO item(name) values("Name 2")', 22 | 'INSERT INTO item(name) values("Name 3")', 23 | ]; 24 | 25 | await db.init( 26 | path: "testdb.sqlite", absolutePath: true, queries: q, verbose: true); 27 | expect(db.isReady, true); 28 | return true; 29 | }); 30 | }); 31 | group("query", () { 32 | test("Insert", () async { 33 | Future insert() async { 34 | final res = 35 | await db.insert(table: "test", row: {"k": "v"}, verbose: true); 36 | return res; 37 | } 38 | 39 | return insert().then((int r) => expect(r, 1)); 40 | }); 41 | 42 | test("Delete", () async { 43 | Future delete() async { 44 | final res = 45 | await db.delete(table: "test", where: "id=1", verbose: true); 46 | return res; 47 | } 48 | 49 | return delete().then((int r) => expect(r, 1)); 50 | }); 51 | 52 | test("Update", () async { 53 | Future update() async { 54 | final res = await db.update( 55 | table: "test", where: "id=1", row: {"k": "v"}, verbose: true); 56 | return res; 57 | } 58 | 59 | return update().then((int r) => expect(r, 1)); 60 | }); 61 | 62 | test("Upsert", () async { 63 | Future upsert() async { 64 | final res = 65 | await db.upsert(table: "test", row: {"k": "v"}, verbose: true); 66 | return res; 67 | } 68 | 69 | return upsert().then((dynamic r) => expect(r, null)); 70 | }); 71 | 72 | test("Select", () async { 73 | Future>> select() async { 74 | final res = 75 | await db.select(table: "test", where: "id=1", verbose: true); 76 | return res; 77 | } 78 | 79 | final output = >[ 80 | {"k": "v"} 81 | ]; 82 | return select().then((List> r) => expect(r, output)); 83 | }); 84 | 85 | test("Join", () async { 86 | Future>> join() async { 87 | final res = await db.join( 88 | table: "test", 89 | joinOn: "table1.id=1", 90 | joinTable: "table1", 91 | where: "id=1", 92 | verbose: true); 93 | return res; 94 | } 95 | 96 | final output = >[ 97 | {"k": "v"} 98 | ]; 99 | return join().then((List> r) => expect(r, output)); 100 | }); 101 | 102 | test("Mjoin", () async { 103 | Future>> mJoin() async { 104 | final res = await db.mJoin( 105 | table: "test", 106 | joinsOn: ["table1.id=1"], 107 | joinsTables: ["table1"], 108 | where: "id=1", 109 | verbose: true); 110 | return res; 111 | } 112 | 113 | final output = >[ 114 | {"k": "v"} 115 | ]; 116 | return mJoin().then((List> r) => expect(r, output)); 117 | }); 118 | 119 | test("Query", () async { 120 | Future>> query() async { 121 | final res = await db.query("SELECT * from test_table"); 122 | return res; 123 | } 124 | 125 | final output = >[ 126 | {"k": "v"} 127 | ]; 128 | return query().then((List> r) => expect(r, output)); 129 | }); 130 | 131 | test("Count", () async { 132 | Future count() async { 133 | final res = await db.count(table: "test", where: "id=1", verbose: true); 134 | return res; 135 | } 136 | 137 | return count().then((int r) => expect(r, 1)); 138 | }); 139 | 140 | test("Exists", () async { 141 | Future exists() async { 142 | final res = 143 | await db.exists(table: "test", where: "id=1", verbose: true); 144 | return res; 145 | } 146 | 147 | return exists().then((bool r) => expect(r, true)); 148 | }); 149 | 150 | test("Batch insert", () async { 151 | Future> batchInsert() async { 152 | final res = await db.batchInsert( 153 | table: "test", 154 | rows: [ 155 | {"k": "v"}, 156 | {"k2": "v2"} 157 | ], 158 | verbose: true); 159 | return res; 160 | } 161 | 162 | return batchInsert() 163 | .then((dynamic r) => expect(r is List, true)); 164 | }); 165 | }); 166 | 167 | group("Select bloc", () { 168 | test("init", () async { 169 | final bloc = SelectBloc(database: db, reactive: true, table: "test"); 170 | var i = 0; 171 | bloc.items.listen((item) { 172 | expect(item, >[ 173 | {"k": "v"}, 174 | {"k": "v"} 175 | ]); 176 | if (i > 0) { 177 | // update finished, dispose 178 | bloc.dispose(); 179 | } 180 | ++i; 181 | }); 182 | bloc.update(>[ 183 | {"k": "v"}, 184 | {"k": "v"} 185 | ]); 186 | final bloc2 = SelectBloc(database: db, query: "SELECT * FROM test"); 187 | bloc2.items.listen((item) { 188 | expect(item, >[ 189 | {"k": "v"}, 190 | {"k": "v"} 191 | ]); 192 | }); 193 | }); 194 | 195 | test("init with join", () async { 196 | final bloc = SelectBloc( 197 | database: db, 198 | table: "test", 199 | joinOn: "test_join.id=join.id", 200 | joinTable: "test_join"); 201 | bloc.items.listen((item) { 202 | //print("ITEM $item"); 203 | expect(item, >[ 204 | {"k": "v"} 205 | ]); 206 | }); 207 | }); 208 | }); 209 | } 210 | -------------------------------------------------------------------------------- /test/dbmodels/car.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | import 'conf.dart' as conf; 4 | import 'manufacturer.dart'; 5 | import 'schema.dart'; 6 | 7 | class Car with DbModel { 8 | Car({this.id, this.name, this.price, this.manufacturer}); 9 | 10 | final String name; 11 | double price; 12 | Manufacturer manufacturer; 13 | 14 | /// [DbModel] required overrides 15 | 16 | @override 17 | int id; 18 | 19 | @override 20 | Db get db => conf.db; 21 | 22 | @override 23 | DbTable get table => carTable; 24 | 25 | @override 26 | Map toDb() { 27 | final row = { 28 | "name": name, 29 | "price": price, 30 | }; 31 | if (manufacturer != null) { 32 | row["manufacturer"] = manufacturer.id; 33 | } 34 | return row; 35 | } 36 | 37 | @override 38 | Car fromDb(Map map) { 39 | final car = Car( 40 | id: map["id"] as int, 41 | name: map["name"].toString(), 42 | price: map["price"] as double, 43 | ); 44 | if (map.containsKey("manufacturer")) { 45 | car.manufacturer = 46 | Manufacturer().fromDb(map["manufacturer"] as Map); 47 | } 48 | return car; 49 | } 50 | 51 | static Future> select({String where, int limit}) async { 52 | final cars = List.from( 53 | await Car().sqlSelect(where: where, limit: limit, verbose: true)); 54 | return cars; 55 | } 56 | 57 | static Future> selectRelated({String where, int limit}) async { 58 | final cars = List.from( 59 | await Car().sqlJoin(where: where, limit: limit, verbose: true)); 60 | return cars; 61 | } 62 | 63 | @override 64 | String toString() { 65 | return "$id: $name / $price"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/dbmodels/conf.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | final Db db = Db(); 4 | -------------------------------------------------------------------------------- /test/dbmodels/manufacturer.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | import 'conf.dart' as conf; 3 | import 'schema.dart'; 4 | 5 | class Manufacturer with DbModel { 6 | Manufacturer({this.id, this.name}); 7 | 8 | final String name; 9 | 10 | /// [DbModel] required overrides 11 | 12 | @override 13 | int id; 14 | 15 | @override 16 | Db get db => conf.db; 17 | 18 | @override 19 | DbTable get table => manufacturerTable; 20 | 21 | @override 22 | Map toDb() => {"name": name}; 23 | 24 | @override 25 | Manufacturer fromDb(Map map) { 26 | return Manufacturer(name: map["name"].toString()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/dbmodels/schema.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqlcool/sqlcool.dart'; 2 | 3 | final DbTable carTable = DbTable("car") 4 | ..varchar("name") 5 | ..real("price") 6 | ..foreignKey("manufacturer", onDelete: OnDelete.cascade); 7 | 8 | final DbTable manufacturerTable = DbTable("manufacturer")..varchar("name"); 9 | -------------------------------------------------------------------------------- /test/errors_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:pedantic/pedantic.dart'; 4 | import 'package:sqlcool/sqlcool.dart'; 5 | import 'base.dart'; 6 | 7 | Future main() async { 8 | await setup(); 9 | 10 | final db = Db(); 11 | final table1 = DbTable("table1")..varchar("name"); 12 | final table = DbTable("table") 13 | ..varchar("name", unique: true, nullable: true) 14 | ..integer("int", defaultValue: 3, unique: true, check: "int>1") 15 | ..real("real") 16 | ..boolean("bool", defaultValue: true) 17 | ..blob("blob") 18 | ..text("text") 19 | ..index("name") 20 | ..timestamp() 21 | ..foreignKey("table1", unique: false, onDelete: OnDelete.cascade) 22 | ..uniqueTogether("name", "int"); 23 | 24 | test("Init", () async { 25 | expect(db.isReady, false); 26 | await db.select(table: "table").catchError((dynamic e) { 27 | expect(e is DatabaseNotReady, true); 28 | expect( 29 | e.message, 30 | 'The Sqlcool database is not ready. This happens when a query' 31 | ' is fired and the database has not finished initializing.'); 32 | }); 33 | await db.insert(table: "table", row: {}).catchError( 34 | (dynamic e) { 35 | expect(e is DatabaseNotReady, true); 36 | }); 37 | await db.upsert(table: "table", row: {}).catchError( 38 | (dynamic e) { 39 | expect(e is DatabaseNotReady, true); 40 | }); 41 | await db 42 | .update(table: "table", row: {}, where: "id=1") 43 | .catchError((dynamic e) { 44 | expect(e is DatabaseNotReady, true); 45 | }); 46 | await db.delete(table: "table", where: "id=1").catchError((dynamic e) { 47 | expect(e is DatabaseNotReady, true); 48 | }); 49 | await db 50 | .join(table: "table", joinOn: "", joinTable: "") 51 | .catchError((dynamic e) { 52 | expect(e is DatabaseNotReady, true); 53 | }); 54 | await db.mJoin( 55 | table: "table", 56 | joinsOn: [], 57 | joinsTables: []).catchError((dynamic e) { 58 | expect(e is DatabaseNotReady, true); 59 | }); 60 | await db.exists(table: "table", where: "id=1").catchError((dynamic e) { 61 | expect(e is DatabaseNotReady, true); 62 | }); 63 | await db.count(table: "table", where: "id=1").catchError((dynamic e) { 64 | expect(e is DatabaseNotReady, true); 65 | }); 66 | await db.batchInsert( 67 | table: "table", rows: >[]).catchError((dynamic e) { 68 | expect(e is DatabaseNotReady, true); 69 | }); 70 | // init 71 | expect(() async => db.init(path: null), 72 | throwsA(predicate((dynamic e) => e is AssertionError))); 73 | unawaited(db.onReady.then((_) { 74 | expect(db.isReady, true); 75 | })); 76 | await db.init( 77 | path: "test_errs_db.sqlite", 78 | schema: [table, table1], 79 | absolutePath: true, 80 | debug: true); 81 | await db.onReady; 82 | expect(db.isReady, true); 83 | expect(db.file.path, File("test_errs_db.sqlite").path); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /test/schema_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:sqlcool/sqlcool.dart'; 3 | import 'base.dart'; 4 | 5 | Future main() async { 6 | await setup(); 7 | 8 | final db = Db(); 9 | final table1 = DbTable("table1")..varchar("name"); 10 | final table = DbTable("table") 11 | ..varchar("name", unique: true, nullable: true) 12 | ..integer("int", defaultValue: 3, unique: true, check: "int>1") 13 | ..real("real") 14 | ..boolean("bool", defaultValue: true) 15 | ..blob("blob") 16 | ..text("text") 17 | ..index("name") 18 | ..timestamp() 19 | ..foreignKey("table1", unique: false, onDelete: OnDelete.cascade) 20 | ..uniqueTogether("name", "int"); 21 | await db.init( 22 | path: "testdb.sqlite", 23 | schema: [table, table1], 24 | absolutePath: true); 25 | 26 | test("DbTable", () async { 27 | expect(db.hasSchema, true); 28 | var t = db.schema.table("table"); 29 | expect(table.name == t.name, true); 30 | expect(db.schema.hasTable("table"), true); 31 | const fkCol = DbColumn( 32 | name: "table1", type: DbColumnType.integer, onDelete: OnDelete.cascade); 33 | expect(t.foreignKeys[0].type, fkCol.type); 34 | expect(t.foreignKeys[0].name, fkCol.name); 35 | expect(t.foreignKeys[0].onDelete, fkCol.onDelete); 36 | t = db.schema.table("table1"); 37 | const col = DbColumn(name: "name", type: DbColumnType.varchar); 38 | expect(t.column("name").name, "name"); 39 | expect(t.columns[0].name, col.name); 40 | expect(t.columns[0].type, col.type); 41 | expect(t.columns[0].name, col.name); 42 | expect(t.columns[0].type, col.type); 43 | }); 44 | 45 | test("Db column", () async { 46 | var col = const DbColumn(name: "col", type: DbColumnType.varchar); 47 | expect(col.typeToString(), "varchar"); 48 | col = const DbColumn(name: "col", type: DbColumnType.integer); 49 | expect(col.typeToString(), "integer"); 50 | col = const DbColumn(name: "col", type: DbColumnType.real); 51 | expect(col.typeToString(), "real"); 52 | col = const DbColumn(name: "col", type: DbColumnType.text); 53 | expect(col.typeToString(), "text"); 54 | col = const DbColumn(name: "col", type: DbColumnType.boolean); 55 | expect(col.typeToString(), "boolean"); 56 | col = const DbColumn(name: "col", type: DbColumnType.blob); 57 | expect(col.typeToString(), "blob"); 58 | expect(stringToOnDelete("restrict"), OnDelete.restrict); 59 | expect(onDeleteToString(OnDelete.restrict), "restrict"); 60 | expect(stringToOnDelete("cascade"), OnDelete.cascade); 61 | expect(onDeleteToString(OnDelete.cascade), "cascade"); 62 | expect(stringToOnDelete("set_null"), OnDelete.setNull); 63 | expect(onDeleteToString(OnDelete.setNull), "set_null"); 64 | expect(stringToOnDelete("set_default"), OnDelete.setDefault); 65 | expect(onDeleteToString(OnDelete.setDefault), "set_default"); 66 | expect(db.schema.table("table").hasColumn("name"), true); 67 | }); 68 | 69 | test("column describe", () async { 70 | const col = DbColumn(name: "col", type: DbColumnType.varchar); 71 | final des = col.describe(isPrint: false); 72 | expect(des, """Column col: 73 | - Type: DbColumnType.varchar 74 | - Unique: false 75 | - Nullable: false 76 | - Default value: null 77 | - Is foreign key: false 78 | - Reference: null 79 | - On delete: null"""); 80 | }); 81 | db.schema.describe(); 82 | } 83 | --------------------------------------------------------------------------------