diff --git a/pubspec.yaml b/pubspec.yaml deleted file mode 100644 index ec90635f..00000000 --- a/pubspec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# This pubspec file exists so that this repository can show up in the generated list of community -# repositories. It's not meant to serve as an actual pub file. - -name: sally -description: Sally is a safe and reactive persistence library for Dart applications -homepage: https://github.com/simolus3/sally -authors: - - Flutter Community - - Simon Binder -maintainer: Simon Binder (@simolus3) - -environment: - sdk: '>=2.0.0 <3.0.0' diff --git a/sally/example/example.dart b/sally/example/example.dart new file mode 100644 index 00000000..afc797d6 --- /dev/null +++ b/sally/example/example.dart @@ -0,0 +1,37 @@ +import 'package:sally/sally.dart'; + +// Define tables that can model a database of recipes. + +@DataClassName('Category') +class Categories extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text().nullable()(); +} + +class Recipes extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(max: 16)(); + TextColumn get instructions => text()(); + IntColumn get category => integer().nullable()(); +} + +class Ingredients extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + IntColumn get caloriesPer100g => integer().named('calories')(); +} + +class IngredientInRecipes extends Table { + + @override + String get tableName => 'recipe_ingredients'; + + // We can also specify custom primary keys + @override + Set get primaryKey => {recipe, ingredient}; + + IntColumn get recipe => integer().autoIncrement()(); + IntColumn get ingredient => integer().autoIncrement()(); + + IntColumn get amountInGrams => integer().named('amount')(); +} diff --git a/sally/lib/src/dsl/columns.dart b/sally/lib/src/dsl/columns.dart index a76c133a..0999dee7 100644 --- a/sally/lib/src/dsl/columns.dart +++ b/sally/lib/src/dsl/columns.dart @@ -40,8 +40,8 @@ class ColumnBuilder { /// `IntColumn get id = integer((c) => c.named('user_id'))`. Builder named(String name) => null; - /// Marks this column as being part of a primary key. This is not yet - /// supported by sally. + @Deprecated('Ignored by the generator. Please override primaryKey in your ' + 'table class instead') Builder primaryKey() => null; /// Marks this column as nullable. Nullable columns should not appear in a diff --git a/sally/lib/src/dsl/table.dart b/sally/lib/src/dsl/table.dart index d5bf579a..27051ad8 100644 --- a/sally/lib/src/dsl/table.dart +++ b/sally/lib/src/dsl/table.dart @@ -20,8 +20,7 @@ abstract class Table { /// In the future, you can override this to specify a custom primary key. This /// is not supported by sally at the moment. @visibleForOverriding - // todo allow custom primary key - PrimaryKey get primaryKey => null; + Set get primaryKey => null; /// Use this as the body of a getter to declare a column that holds integers. /// Example (inside the body of a table class): @@ -57,8 +56,6 @@ abstract class Table { DateTimeColumnBuilder dateTime() => null; } -class PrimaryKey {} - /// A class to to be used as an annotation on [Table] classes to customize the /// name for the data class that will be generated for the table class. The data /// class is a dart object that will be used to represent a row in the table. diff --git a/sally/pubspec.yaml b/sally/pubspec.yaml index f2b8fcfc..2cb84ef2 100644 --- a/sally/pubspec.yaml +++ b/sally/pubspec.yaml @@ -3,7 +3,6 @@ description: Sally is a safe and reactive persistence library for Dart applicati version: 1.0.0 homepage: https://github.com/simolus3/sally authors: - - Flutter Community - Simon Binder maintainer: Simon Binder (@simolus3) @@ -19,4 +18,4 @@ dev_dependencies: build_runner: ^1.2.0 build_test: ^0.10.6 test: ^1.5.3 - mockito: ^4.0.0 \ No newline at end of file + mockito: ^4.0.0 diff --git a/sally_flutter/pubspec.yaml b/sally_flutter/pubspec.yaml index 771c21df..ae50c86f 100644 --- a/sally_flutter/pubspec.yaml +++ b/sally_flutter/pubspec.yaml @@ -2,7 +2,6 @@ name: sally_flutter description: Flutter implementation of sally, a safe and reactive persistence library for Dart applications version: 1.0.0 authors: - - Flutter Community - Simon Binder maintainer: Simon Binder (@simolus3) diff --git a/sally_generator/lib/src/model/specified_table.dart b/sally_generator/lib/src/model/specified_table.dart index d8392b14..c2ee1fea 100644 --- a/sally_generator/lib/src/model/specified_table.dart +++ b/sally_generator/lib/src/model/specified_table.dart @@ -11,11 +11,13 @@ class SpecifiedTable { String get tableInfoName => tableInfoNameForTableClass(fromClass); - // todo support primary keys - Set get primaryKey => {}; + /// The set of primary keys, if they have been explicitly defined by + /// overriding `primaryKey` in the table class. `null` if the primary key has + /// not been defined that way. + final Set primaryKey; const SpecifiedTable( - {this.fromClass, this.columns, this.sqlName, this.dartTypeName}); + {this.fromClass, this.columns, this.sqlName, this.dartTypeName, this.primaryKey}); } String tableInfoNameForTableClass(ClassElement fromClass) => diff --git a/sally_generator/lib/src/parser/table_parser.dart b/sally_generator/lib/src/parser/table_parser.dart index ca973963..904e5e20 100644 --- a/sally_generator/lib/src/parser/table_parser.dart +++ b/sally_generator/lib/src/parser/table_parser.dart @@ -17,11 +17,15 @@ class TableParser extends ParserBase { final sqlName = _parseTableName(element); if (sqlName == null) return null; + final columns = _parseColumns(element); + return SpecifiedTable( - fromClass: element, - columns: _parseColumns(element), - sqlName: escapeIfNeeded(sqlName), - dartTypeName: _readDartTypeName(element)); + fromClass: element, + columns: columns, + sqlName: escapeIfNeeded(sqlName), + dartTypeName: _readDartTypeName(element), + primaryKey: _readPrimaryKey(element, columns), + ); } String _readDartTypeName(ClassElement element) { @@ -64,6 +68,39 @@ class TableParser extends ParserBase { return tableName; } + Set _readPrimaryKey(ClassElement element, List columns) { + final primaryKeyGetter = element.getGetter('primaryKey'); + if (primaryKeyGetter == null) { + return null; + } + + final ast = generator.loadElementDeclaration(primaryKeyGetter).node as MethodDeclaration; + final body = ast.body; + if (body is! ExpressionFunctionBody) { + generator.errors.add(SallyError(affectedElement: primaryKeyGetter, message: 'This must return a set literal using the => syntax!')); + return null; + } + final expression = (body as ExpressionFunctionBody).expression; + // set expressions {x, y} are parsed as map literals whose values are an empty + // identifier {x: , y: }. yeah. + // todo should we support MapLiteral2 to support the experiments discussed there? + if (expression is! MapLiteral) { + generator.errors.add(SallyError(affectedElement: primaryKeyGetter, message: 'This must return a set literal!')); + return null; + } + final mapLiteral = expression as MapLiteral; + + final parsedPrimaryKey = {}; + + for (var entry in mapLiteral.entries) { + final key = entry.key as Identifier; + final column = columns.singleWhere((column) => column.dartGetterName == key.name); + parsedPrimaryKey.add(column); + } + + return parsedPrimaryKey; + } + Iterable _findColumnGetters(ClassElement element) { return element.fields .where((field) => isColumn(field.type) && field.getter != null) diff --git a/sally_generator/pubspec.yaml b/sally_generator/pubspec.yaml index dbd45fd6..6c8cbba2 100644 --- a/sally_generator/pubspec.yaml +++ b/sally_generator/pubspec.yaml @@ -1,9 +1,8 @@ name: sally_generator -description: Sally generator generated database code from your table classes +description: Sally generator generates database code from your table classes version: 1.0.0 homepage: https://github.com/simolus3/sally authors: - - Flutter Community - Simon Binder maintainer: Simon Binder (@simolus3) diff --git a/sally_generator/test/parser/parser_test.dart b/sally_generator/test/parser/parser_test.dart index cc06901a..77bdd2da 100644 --- a/sally_generator/test/parser/parser_test.dart +++ b/sally_generator/test/parser/parser_test.dart @@ -27,6 +27,14 @@ void main() async { TextColumn get onlyMax => text().withLength(max: 100)(); } + class CustomPrimaryKey extends Table { + IntColumn get partA => integer()(); + IntColumn get partB => integer()(); + + @override + Set get primaryKey => {partA, partB}; + } + class WrongName extends Table { String constructTableName() { @@ -34,7 +42,7 @@ void main() async { } @override - String get tableName => constructTableName();" + String get tableName => constructTableName(); } ''', (r) => r.findLibraryByName('test_parser')); }); @@ -102,4 +110,10 @@ void main() async { idColumn.features, contains(LimitingTextLength.withLength(max: 100))); }); }); + + test('parses custom primary keys', () { + final table = TableParser(generator).parse(testLib.getType('CustomPrimaryKey')); + + expect(table.primaryKey, containsAll(table.columns)); + }); }