diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 015b7d90..ea5b46d5 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,28 +5,27 @@ - - - - @@ -34,7 +33,6 @@ - @@ -42,77 +40,69 @@ - - - - - - - - - - - - - - - - @@ -120,14 +110,13 @@ - - @@ -135,35 +124,35 @@ - - - - - @@ -184,98 +173,97 @@ - - - - - - - - - - - - - - @@ -283,7 +271,6 @@ - @@ -291,7 +278,7 @@ - @@ -305,35 +292,34 @@ - - - - - @@ -341,42 +327,41 @@ - - - - - - @@ -384,35 +369,35 @@ - - - - - @@ -426,28 +411,28 @@ - - - - @@ -462,7 +447,6 @@ - @@ -470,7 +454,6 @@ - @@ -478,14 +461,13 @@ - - @@ -500,7 +482,7 @@ - @@ -508,14 +490,13 @@ - - @@ -523,21 +504,20 @@ - - - @@ -545,7 +525,7 @@ - @@ -559,127 +539,113 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md deleted file mode 100644 index fe310663..00000000 --- a/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Sally -[![Build Status](https://travis-ci.com/simolus3/sally.svg?token=u4VnFEE5xnWVvkE6QsqL&branch=master)](https://travis-ci.com/simolus3/sally) - -Sally is an easy to use and safe way to persist data for Flutter apps. It features -a fluent Dart DSL to describe tables and will generate matching database code that -can be used to easily read and store your app's data. - -__Note:__ This library is in development and not yet available for general use on `pub`. - -## Using this library -#### Adding the dependency -First, let's add sally to your prooject's `pubspec.yaml`: -```yaml -dependencies: - sally: - git: - url: - path: sally/ - -dev_dependencies: - sally_generator: - git: - url: - path: sally_generator/ - build_runner: -``` -We're going to use the `sally` library to specify tables and write data. The -`sally_generator` library will take care of generating the necessary code so the -library knows how your table structure looks like. - -#### Declaring tables -You can use the DSL included with this library to specify your libraries with simple -dart code: -```dart -import 'package:sally/sally.dart'; - -// assuming that your file is called filename.dart. This will give an error at first, -// but it's needed for sally to know about the generated code -part 'filename.g.dart'; - -class Todos extends Table { - IntColumn get id => integer().autoIncrement()(); - TextColumn get name => text().withLength(min: 6, max: 10)(); - TextColumn get content => text().named('body')(); - IntColumn get category => integer()(); -} - -class Categories extends Table { - @override - String get tableName => 'todo_categories'; - - IntColumn get id => integer().autoIncrement()(); - TextColumn get description => text()(); -} - -@UseSally(tables: [Todos, Categories]) -class MyDatabase { - -} -``` - -__⚠️ Warning:__ Even though it might look like it, the content of a `Table` class does not support full Dart code. It can only -be used to declare the table name, it's primary keys and columns. The code inside of a table class will never be -executed. Instead, the generator will take a look at your table classes to figure out how their structure looks like. -This won't work if the body of your tables is not constant. This should not be problem, but please be aware of this. - -#### Generating the code -Sally integrates with the dart `build` system, so you can generate all the code needed with -`flutter packages pub run build_runner build`. If you want to continously rebuild the code -whever you change your code, run `flutter packages pub run build_runner watch` instead. -After running either command once, sally generator will have created a class for your -database and data classes for your entities. To use it, change the `MyDatabase` class as -follows: -```dart -@UseSally(tables: [Todos, Categories]) -class MyDatabase extends _$MyDatabase { - @override - int get schemaVersion => 1; - @override - MigrationStrategy get migration => MigrationStrategy(); -} -``` -You can ignore these two getters there at the moment, the imporant part is that you can -now run your queries with fluent Dart code: -```dart -class MyDatabase extends _$MyDatabase { - // .. the getters that have been defined above still need to be here - - Future> get allTodoEntries => select(todos).get(); - - Future deleteCategory(Category toDelete) async { - await (delete(todos)..where((entry) => entry.category.equalsVal(category.id))).go(); - await (delete(categories)..where((cat) => cat.id.equalsVal(toDelete.id))).go(); - } -} -``` - -## TODO-List -If you have suggestions for new features or any other questions, feel free to -create an issue. - -##### Before this library can be released -- Insert and update statements -- Custom primary keys -- Stabilize all end-user APIs -- Support default values and expressions, auto-increment -- Implement `==` and `hashCode` in data classes -- Allow custom table names for the generated dart types -##### Definitely planned for the future -- Allow using DAOs instead of having to put everything in the main database -class. -- Auto-updating streams -- Support more Datatypes: We should at least support `DateTime` and `Uint8List`, -supporting floating point numbers as well would be awesome -- Nullable / non-nullable datatypes - - DSL API - - Support in generator - - Use in queries (`IS NOT NULL`) -- Verify constraints (text length, nullability, etc.) before inserting or - deleting data. -- Support Dart VM apps -- References -- Table joins -##### Interesting stuff that would be nice to have -- `GROUP BY` grouping functions -- Support for different database engines - - Support webapps via `AlaSQL` or a different engine \ No newline at end of file diff --git a/README.md b/README.md new file mode 120000 index 00000000..f00e0b69 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +sally/README.md \ No newline at end of file diff --git a/example/.flutter-plugins b/example/.flutter-plugins new file mode 100644 index 00000000..3e4cc2a8 --- /dev/null +++ b/example/.flutter-plugins @@ -0,0 +1 @@ +sqflite=/home/simon/Android/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.1.0/ diff --git a/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 00000000..aff2f141 --- /dev/null +++ b/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,25 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; +import com.tekartik.sqflite.SqflitePlugin; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin")); + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/example/ios/Runner/GeneratedPluginRegistrant.h b/example/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 00000000..3b700eb4 --- /dev/null +++ b/example/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +#endif /* GeneratedPluginRegistrant_h */ diff --git a/example/ios/Runner/GeneratedPluginRegistrant.m b/example/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 00000000..8abd91ed --- /dev/null +++ b/example/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" +#import + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { + [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]]; +} + +@end diff --git a/example/lib/example.g.dart b/example/lib/example.g.dart index 49fa8db6..0fb49d05 100644 --- a/example/lib/example.g.dart +++ b/example/lib/example.g.dart @@ -10,6 +10,12 @@ class Product { final int id; final String name; Product({this.id, this.name}); + @override + int get hashCode => (id.hashCode) * 31 + name.hashCode; + @override + bool operator ==(other) => + identical(this, other) || + (other is Product && other.id == id && other.name == name); } class _$ProductsTable extends Products implements TableInfo { @@ -42,6 +48,12 @@ class User { final int id; final String name; User({this.id, this.name}); + @override + int get hashCode => (id.hashCode) * 31 + name.hashCode; + @override + bool operator ==(other) => + identical(this, other) || + (other is User && other.id == id && other.name == name); } class _$UsersTable extends Users implements TableInfo { @@ -75,6 +87,16 @@ class Todo { final String name; final String content; Todo({this.id, this.name, this.content}); + @override + int get hashCode => + ((id.hashCode) * 31 + name.hashCode) * 31 + content.hashCode; + @override + bool operator ==(other) => + identical(this, other) || + (other is Todo && + other.id == id && + other.name == name && + other.content == content); } class _$TodosTable extends Todos implements TableInfo { @@ -85,7 +107,7 @@ class _$TodosTable extends Todos implements TableInfo { @override GeneratedTextColumn get name => GeneratedTextColumn('name', false); @override - GeneratedTextColumn get content => GeneratedTextColumn('content', false); + GeneratedTextColumn get content => GeneratedTextColumn('body', false); @override List get $columns => [id, name, content]; @override @@ -101,7 +123,7 @@ class _$TodosTable extends Todos implements TableInfo { return Todo( id: intType.mapFromDatabaseResponse(data['id']), name: stringType.mapFromDatabaseResponse(data['name']), - content: stringType.mapFromDatabaseResponse(data['content']), + content: stringType.mapFromDatabaseResponse(data['body']), ); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 09b3e646..a650e14c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ description: A sample command-line application. # author: simon environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dependencies: sally: diff --git a/sally/.gitignore b/sally/.gitignore index dc13152b..d965bf94 100644 --- a/sally/.gitignore +++ b/sally/.gitignore @@ -25,6 +25,9 @@ doc/api/ *.js.deps *.js.map +android/ +ios/ + ### Intellij ### .idea/**/* # End of https://www.gitignore.io/api/dart,intellij diff --git a/sally/README.md b/sally/README.md index 5afc1b21..c22ef754 100644 --- a/sally/README.md +++ b/sally/README.md @@ -1,22 +1,126 @@ -A library for Dart developers. +# Sally +[![Build Status](https://travis-ci.com/simolus3/sally.svg?token=u4VnFEE5xnWVvkE6QsqL&branch=master)](https://travis-ci.com/simolus3/sally) -Created from templates made available by Stagehand under a BSD-style -[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). +Sally is an easy to use and safe way to persist data for Flutter apps. It features +a fluent Dart DSL to describe tables and will generate matching database code that +can be used to easily read and store your app's data. -## Usage +__Note:__ This library is in development and not yet available for general use on `pub`. -A simple usage example: +## Using this library +#### Adding the dependency +First, let's add sally to your prooject's `pubspec.yaml`: +```yaml +dependencies: + sally: + git: + url: + path: sally/ +dev_dependencies: + sally_generator: + git: + url: + path: sally_generator/ + build_runner: +``` +We're going to use the `sally` library to specify tables and write data. The +`sally_generator` library will take care of generating the necessary code so the +library knows how your table structure looks like. + +#### Declaring tables +You can use the DSL included with this library to specify your libraries with simple +dart code: ```dart import 'package:sally/sally.dart'; -main() { - var awesome = new Awesome(); +// assuming that your file is called filename.dart. This will give an error at first, +// but it's needed for sally to know about the generated code +part 'filename.g.dart'; + +class Todos extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text().withLength(min: 6, max: 10)(); + TextColumn get content => text().named('body')(); + IntColumn get category => integer()(); +} + +class Categories extends Table { + @override + String get tableName => 'todo_categories'; + + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); +} + +@UseSally(tables: [Todos, Categories]) +class MyDatabase { + } ``` -## Features and bugs +__⚠️ Warning:__ Even though it might look like it, the content of a `Table` class does not support full Dart code. It can only +be used to declare the table name, it's primary keys and columns. The code inside of a table class will never be +executed. Instead, the generator will take a look at your table classes to figure out how their structure looks like. +This won't work if the body of your tables is not constant. This should not be problem, but please be aware of this. -Please file feature requests and bugs at the [issue tracker][tracker]. +#### Generating the code +Sally integrates with the dart `build` system, so you can generate all the code needed with +`flutter packages pub run build_runner build`. If you want to continously rebuild the code +whever you change your code, run `flutter packages pub run build_runner watch` instead. +After running either command once, sally generator will have created a class for your +database and data classes for your entities. To use it, change the `MyDatabase` class as +follows: +```dart +@UseSally(tables: [Todos, Categories]) +class MyDatabase extends _$MyDatabase { + @override + int get schemaVersion => 1; + @override + MigrationStrategy get migration => MigrationStrategy(); +} +``` +You can ignore these two getters there at the moment, the imporant part is that you can +now run your queries with fluent Dart code: +```dart +class MyDatabase extends _$MyDatabase { + // .. the getters that have been defined above still need to be here -[tracker]: http://example.com/issues/replaceme + Future> get allTodoEntries => select(todos).get(); + + Future deleteCategory(Category toDelete) async { + await (delete(todos)..where((entry) => entry.category.equalsVal(category.id))).go(); + await (delete(categories)..where((cat) => cat.id.equalsVal(toDelete.id))).go(); + } +} +``` + +## TODO-List +If you have suggestions for new features or any other questions, feel free to +create an issue. + +##### Before this library can be released +- Insert and update statements +- Custom primary keys +- Stabilize all end-user APIs +- Support default values and expressions, auto-increment +- Allow custom table names for the generated dart types +##### Definitely planned for the future +- Allow using DAOs instead of having to put everything in the main database +class. +- Auto-updating streams +- Support more Datatypes: We should at least support `DateTime` and `Uint8List`, +supporting floating point numbers as well would be awesome +- Nullable / non-nullable datatypes + - DSL API + - Support in generator + - Use in queries (`IS NOT NULL`) +- Verify constraints (text length, nullability, etc.) before inserting or + deleting data. +- Support Dart VM apps +- References +- Table joins +##### Interesting stuff that would be nice to have +- `GROUP BY` grouping functions +- Support for different database engines + - Support webapps via `AlaSQL` or a different engine \ No newline at end of file diff --git a/sally_generator/.flutter-plugins b/sally_generator/.flutter-plugins new file mode 100644 index 00000000..3e4cc2a8 --- /dev/null +++ b/sally_generator/.flutter-plugins @@ -0,0 +1 @@ +sqflite=/home/simon/Android/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.1.0/ diff --git a/sally_generator/.gitignore b/sally_generator/.gitignore index f95ca619..fac28d89 100644 --- a/sally_generator/.gitignore +++ b/sally_generator/.gitignore @@ -25,6 +25,9 @@ doc/api/ *.js.deps *.js.map +android/ +ios/ + ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 diff --git a/sally_generator/lib/src/writer/data_class_writer.dart b/sally_generator/lib/src/writer/data_class_writer.dart new file mode 100644 index 00000000..f5a330b6 --- /dev/null +++ b/sally_generator/lib/src/writer/data_class_writer.dart @@ -0,0 +1,67 @@ +import 'package:sally_generator/src/model/specified_table.dart'; + +class DataClassWriter { + final SpecifiedTable table; + + DataClassWriter(this.table); + + void writeInto(StringBuffer buffer) { + buffer.write('class ${table.dartTypeName} {\n'); + + // write individual fields + for (var column in table.columns) { + buffer.write('final ${column.dartTypeName} ${column.dartGetterName}; \n'); + } + + // write constructor with named optional fields + buffer + ..write(table.dartTypeName) + ..write('({') + ..write(table.columns + .map((column) => 'this.${column.dartGetterName}') + .join(', ')) + ..write('});') + ..write('@override\n int get hashCode => '); + + if (table.columns.isEmpty) { + buffer.write('identityHashCode(this); \n'); + } else { + final fields = table.columns.map((c) => c.dartGetterName).toList(); + buffer..write(_calculateHashCode(fields))..write('; \n'); + } + + // override == + // return identical(this, other) || (other is Todo && other.id == id && other.) + buffer + ..write('@override\nbool operator ==(other) => ') + ..write('identical(this, other) || (other is ${table.dartTypeName}'); + + if (table.columns.isNotEmpty) { + buffer + ..write('&&') + ..write(table.columns.map((c) { + final getter = c.dartGetterName; + + return 'other.$getter == $getter'; + }).join(' && ')); + } + + // finish overrides method and class declaration + buffer.write(');\n}'); + } + + /// Recursively creates the implementation for hashCode of the data class, + /// assuming it has at least one field. When it has one field, we just return + /// the hash code of that field. Otherwise, we multiply it with 31 and add + /// the hash code of the next field, and so on. + String _calculateHashCode(List fields) { + if (fields.length == 1) { + return '${fields.last}.hashCode'; + } else { + final last = fields.removeLast(); + final innerHash = _calculateHashCode(fields); + + return '($innerHash) * 31 + $last.hashCode'; + } + } +} diff --git a/sally_generator/lib/src/writer/table_writer.dart b/sally_generator/lib/src/writer/table_writer.dart index e8eec86b..e21adc98 100644 --- a/sally_generator/lib/src/writer/table_writer.dart +++ b/sally_generator/lib/src/writer/table_writer.dart @@ -1,5 +1,6 @@ import 'package:recase/recase.dart'; import 'package:sally_generator/src/model/specified_table.dart'; +import 'package:sally_generator/src/writer/data_class_writer.dart'; class TableWriter { final SpecifiedTable table; @@ -12,22 +13,7 @@ class TableWriter { } void writeDataClass(StringBuffer buffer) { - buffer.write('class ${table.dartTypeName} {\n'); - - // write individual fields - for (var column in table.columns) { - buffer.write('final ${column.dartTypeName} ${column.dartGetterName}; \n'); - } - - // write constructor with named optional fields - buffer - ..write(table.dartTypeName) - ..write('({') - ..write(table.columns - .map((column) => 'this.${column.dartGetterName}') - .join(', ')) - ..write('});') - ..write('\n}'); + DataClassWriter(table).writeInto(buffer); } void writeTableInfoClass(StringBuffer buffer) { diff --git a/sally_generator/pubspec.yaml b/sally_generator/pubspec.yaml index ff8ab10c..4f73c53c 100644 --- a/sally_generator/pubspec.yaml +++ b/sally_generator/pubspec.yaml @@ -5,7 +5,7 @@ description: A starting point for Dart libraries or applications. # author: Simon Binder environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dependencies: analyzer: '< 0.35.0'