Custom primary keys

This commit is contained in:
Simon Binder 2019-03-06 21:43:16 +01:00
parent 83f12a71b6
commit 2e7c079e4d
No known key found for this signature in database
GPG Key ID: B807FDF954BA00CF
15 changed files with 596 additions and 55 deletions

View File

@ -1,5 +1,7 @@
import 'package:sally/sally.dart';
part 'example.g.dart';
// Define tables that can model a database of recipes.
@DataClassName('Category')
@ -22,7 +24,6 @@ class Ingredients extends Table {
}
class IngredientInRecipes extends Table {
@override
String get tableName => 'recipe_ingredients';
@ -30,8 +31,22 @@ class IngredientInRecipes extends Table {
@override
Set<Column> get primaryKey => {recipe, ingredient};
IntColumn get recipe => integer().autoIncrement()();
IntColumn get ingredient => integer().autoIncrement()();
IntColumn get recipe => integer()();
IntColumn get ingredient => integer()();
IntColumn get amountInGrams => integer().named('amount')();
}
@UseSally(tables: [Categories, Recipes, Ingredients, IngredientInRecipes])
class Database extends _$Database {
Database(QueryExecutor e) : super(e);
@override
int get schemaVersion => 1;
@override
MigrationStrategy get migration => MigrationStrategy(onFinished: () async {
// populate data
await into(categories).insert(Category(description: 'Sweets'));
});
}

View File

@ -0,0 +1,349 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'example.dart';
// **************************************************************************
// SallyGenerator
// **************************************************************************
class Category {
final int id;
final String description;
Category({this.id, this.description});
factory Category.fromData(Map<String, dynamic> data, GeneratedDatabase db) {
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
return Category(
id: intType.mapFromDatabaseResponse(data['id']),
description: stringType.mapFromDatabaseResponse(data['description']),
);
}
Category copyWith({int id, String description}) => Category(
id: id ?? this.id,
description: description ?? this.description,
);
@override
int get hashCode => (id.hashCode) * 31 + description.hashCode;
@override
bool operator ==(other) =>
identical(this, other) ||
(other is Category && other.id == id && other.description == description);
}
class $CategoriesTable extends Categories
implements TableInfo<Categories, Category> {
final GeneratedDatabase _db;
$CategoriesTable(this._db);
@override
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get description => GeneratedTextColumn(
'description',
true,
);
@override
List<GeneratedColumn> get $columns => [id, description];
@override
Categories get asDslTable => this;
@override
String get $tableName => 'categories';
@override
bool validateIntegrity(Category instance, bool isInserting) =>
id.isAcceptableValue(instance.id, isInserting) &&
description.isAcceptableValue(instance.description, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => null;
@override
Category map(Map<String, dynamic> data) {
return Category.fromData(data, _db);
}
@override
Map<String, Variable> entityToSql(Category d) {
final map = <String, Variable>{};
if (d.id != null) {
map['id'] = Variable<int, IntType>(d.id);
}
if (d.description != null) {
map['description'] = Variable<String, StringType>(d.description);
}
return map;
}
}
class Recipe {
final int id;
final String title;
final String instructions;
final int category;
Recipe({this.id, this.title, this.instructions, this.category});
factory Recipe.fromData(Map<String, dynamic> data, GeneratedDatabase db) {
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
return Recipe(
id: intType.mapFromDatabaseResponse(data['id']),
title: stringType.mapFromDatabaseResponse(data['title']),
instructions: stringType.mapFromDatabaseResponse(data['instructions']),
category: intType.mapFromDatabaseResponse(data['category']),
);
}
Recipe copyWith({int id, String title, String instructions, int category}) =>
Recipe(
id: id ?? this.id,
title: title ?? this.title,
instructions: instructions ?? this.instructions,
category: category ?? this.category,
);
@override
int get hashCode =>
(((id.hashCode) * 31 + title.hashCode) * 31 + instructions.hashCode) *
31 +
category.hashCode;
@override
bool operator ==(other) =>
identical(this, other) ||
(other is Recipe &&
other.id == id &&
other.title == title &&
other.instructions == instructions &&
other.category == category);
}
class $RecipesTable extends Recipes implements TableInfo<Recipes, Recipe> {
final GeneratedDatabase _db;
$RecipesTable(this._db);
@override
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get title =>
GeneratedTextColumn('title', false, maxTextLength: 16);
@override
GeneratedTextColumn get instructions => GeneratedTextColumn(
'instructions',
false,
);
@override
GeneratedIntColumn get category => GeneratedIntColumn(
'category',
true,
);
@override
List<GeneratedColumn> get $columns => [id, title, instructions, category];
@override
Recipes get asDslTable => this;
@override
String get $tableName => 'recipes';
@override
bool validateIntegrity(Recipe instance, bool isInserting) =>
id.isAcceptableValue(instance.id, isInserting) &&
title.isAcceptableValue(instance.title, isInserting) &&
instructions.isAcceptableValue(instance.instructions, isInserting) &&
category.isAcceptableValue(instance.category, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => null;
@override
Recipe map(Map<String, dynamic> data) {
return Recipe.fromData(data, _db);
}
@override
Map<String, Variable> entityToSql(Recipe d) {
final map = <String, Variable>{};
if (d.id != null) {
map['id'] = Variable<int, IntType>(d.id);
}
if (d.title != null) {
map['title'] = Variable<String, StringType>(d.title);
}
if (d.instructions != null) {
map['instructions'] = Variable<String, StringType>(d.instructions);
}
if (d.category != null) {
map['category'] = Variable<int, IntType>(d.category);
}
return map;
}
}
class Ingredient {
final int id;
final String name;
final int caloriesPer100g;
Ingredient({this.id, this.name, this.caloriesPer100g});
factory Ingredient.fromData(Map<String, dynamic> data, GeneratedDatabase db) {
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
return Ingredient(
id: intType.mapFromDatabaseResponse(data['id']),
name: stringType.mapFromDatabaseResponse(data['name']),
caloriesPer100g: intType.mapFromDatabaseResponse(data['calories']),
);
}
Ingredient copyWith({int id, String name, int caloriesPer100g}) => Ingredient(
id: id ?? this.id,
name: name ?? this.name,
caloriesPer100g: caloriesPer100g ?? this.caloriesPer100g,
);
@override
int get hashCode =>
((id.hashCode) * 31 + name.hashCode) * 31 + caloriesPer100g.hashCode;
@override
bool operator ==(other) =>
identical(this, other) ||
(other is Ingredient &&
other.id == id &&
other.name == name &&
other.caloriesPer100g == caloriesPer100g);
}
class $IngredientsTable extends Ingredients
implements TableInfo<Ingredients, Ingredient> {
final GeneratedDatabase _db;
$IngredientsTable(this._db);
@override
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get name => GeneratedTextColumn(
'name',
false,
);
@override
GeneratedIntColumn get caloriesPer100g => GeneratedIntColumn(
'calories',
false,
);
@override
List<GeneratedColumn> get $columns => [id, name, caloriesPer100g];
@override
Ingredients get asDslTable => this;
@override
String get $tableName => 'ingredients';
@override
bool validateIntegrity(Ingredient instance, bool isInserting) =>
id.isAcceptableValue(instance.id, isInserting) &&
name.isAcceptableValue(instance.name, isInserting) &&
caloriesPer100g.isAcceptableValue(instance.caloriesPer100g, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => null;
@override
Ingredient map(Map<String, dynamic> data) {
return Ingredient.fromData(data, _db);
}
@override
Map<String, Variable> entityToSql(Ingredient d) {
final map = <String, Variable>{};
if (d.id != null) {
map['id'] = Variable<int, IntType>(d.id);
}
if (d.name != null) {
map['name'] = Variable<String, StringType>(d.name);
}
if (d.caloriesPer100g != null) {
map['calories'] = Variable<int, IntType>(d.caloriesPer100g);
}
return map;
}
}
class IngredientInRecipe {
final int recipe;
final int ingredient;
final int amountInGrams;
IngredientInRecipe({this.recipe, this.ingredient, this.amountInGrams});
factory IngredientInRecipe.fromData(
Map<String, dynamic> data, GeneratedDatabase db) {
final intType = db.typeSystem.forDartType<int>();
return IngredientInRecipe(
recipe: intType.mapFromDatabaseResponse(data['recipe']),
ingredient: intType.mapFromDatabaseResponse(data['ingredient']),
amountInGrams: intType.mapFromDatabaseResponse(data['amount']),
);
}
IngredientInRecipe copyWith(
{int recipe, int ingredient, int amountInGrams}) =>
IngredientInRecipe(
recipe: recipe ?? this.recipe,
ingredient: ingredient ?? this.ingredient,
amountInGrams: amountInGrams ?? this.amountInGrams,
);
@override
int get hashCode =>
((recipe.hashCode) * 31 + ingredient.hashCode) * 31 +
amountInGrams.hashCode;
@override
bool operator ==(other) =>
identical(this, other) ||
(other is IngredientInRecipe &&
other.recipe == recipe &&
other.ingredient == ingredient &&
other.amountInGrams == amountInGrams);
}
class $IngredientInRecipesTable extends IngredientInRecipes
implements TableInfo<IngredientInRecipes, IngredientInRecipe> {
final GeneratedDatabase _db;
$IngredientInRecipesTable(this._db);
@override
GeneratedIntColumn get recipe => GeneratedIntColumn(
'recipe',
false,
);
@override
GeneratedIntColumn get ingredient => GeneratedIntColumn(
'ingredient',
false,
);
@override
GeneratedIntColumn get amountInGrams => GeneratedIntColumn(
'amount',
false,
);
@override
List<GeneratedColumn> get $columns => [recipe, ingredient, amountInGrams];
@override
IngredientInRecipes get asDslTable => this;
@override
String get $tableName => 'recipe_ingredients';
@override
bool validateIntegrity(IngredientInRecipe instance, bool isInserting) =>
recipe.isAcceptableValue(instance.recipe, isInserting) &&
ingredient.isAcceptableValue(instance.ingredient, isInserting) &&
amountInGrams.isAcceptableValue(instance.amountInGrams, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => {recipe, ingredient};
@override
IngredientInRecipe map(Map<String, dynamic> data) {
return IngredientInRecipe.fromData(data, _db);
}
@override
Map<String, Variable> entityToSql(IngredientInRecipe d) {
final map = <String, Variable>{};
if (d.recipe != null) {
map['recipe'] = Variable<int, IntType>(d.recipe);
}
if (d.ingredient != null) {
map['ingredient'] = Variable<int, IntType>(d.ingredient);
}
if (d.amountInGrams != null) {
map['amount'] = Variable<int, IntType>(d.amountInGrams);
}
return map;
}
}
abstract class _$Database extends GeneratedDatabase {
_$Database(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
$CategoriesTable get categories => $CategoriesTable(this);
$RecipesTable get recipes => $RecipesTable(this);
$IngredientsTable get ingredients => $IngredientsTable(this);
$IngredientInRecipesTable get ingredientInRecipes =>
$IngredientInRecipesTable(this);
@override
List<TableInfo> get allTables =>
[categories, recipes, ingredients, ingredientInRecipes];
}

View File

@ -40,10 +40,6 @@ class ColumnBuilder<Builder, ResultColumn> {
/// `IntColumn get id = integer((c) => c.named('user_id'))`.
Builder named(String name) => null;
@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
/// primary key. Columns are non-null by default.
Builder nullable() => null;

View File

@ -17,8 +17,23 @@ abstract class Table {
@visibleForOverriding
String get tableName => null;
/// In the future, you can override this to specify a custom primary key. This
/// is not supported by sally at the moment.
/// Override this to specify custom primary keys:
/// ```dart
/// class IngredientInRecipes extends Table {
/// @override
/// Set<Column> get primaryKey => {recipe, ingredient};
///
/// IntColumn get recipe => integer().autoIncrement()();
/// IntColumn get ingredient => integer().autoIncrement()();
///
/// IntColumn get amountInGrams => integer().named('amount')();
///}
/// ```
/// The getter must return a set literal using the `=>` syntax so that the
/// sally generator can understand the code.
/// Also, please not that it's an error to have a
/// [IntColumnBuilder.autoIncrement] column and a custom primary key.
/// Writing such table in sql will throw at runtime.
@visibleForOverriding
Set<Column> get primaryKey => null;

View File

@ -7,6 +7,10 @@ import 'package:sally/src/runtime/structure/table_info.dart';
typedef Future<void> OnCreate(Migrator m);
typedef Future<void> OnUpgrade(Migrator m, int from, int to);
/// Signature of a function that's called after a migration has finished and the
/// database is ready to be used. Useful to populate data.
typedef Future<void> OnMigrationFinished();
Future<void> _defaultOnCreate(Migrator m) => m.createAllTables();
Future<void> _defaultOnUpdate(Migrator m, int from, int to) async =>
throw Exception("You've bumped the schema version for your sally database "
@ -21,9 +25,15 @@ class MigrationStrategy {
/// happened at a lower [GeneratedDatabase.schemaVersion].
final OnUpgrade onUpgrade;
/// Executes after the database is ready and all migrations ran, but before
/// any other queries will be executed, making this method suitable to
/// populate data.
final OnMigrationFinished onFinished;
MigrationStrategy({
this.onCreate = _defaultOnCreate,
this.onUpgrade = _defaultOnUpdate,
this.onFinished,
});
}
@ -59,6 +69,19 @@ class Migrator {
if (i < table.$columns.length - 1) sql.write(', ');
}
if (table.$primaryKey != null) {
sql.write(', PRIMARY KEY (');
final pkList = table.$primaryKey.toList(growable: false);
for (var i = 0; i < pkList.length; i++) {
final column = pkList[i];
sql.write(column.$name);
if (i != pkList.length - 1) sql.write(', ');
}
sql.write(')');
}
sql.write(');');
return issueCustomQuery(sql.toString());

View File

@ -27,7 +27,15 @@ class Categories extends Table {
TextColumn get description => text().named('desc')();
}
@UseSally(tables: [TodosTable, Categories, Users])
class SharedTodos extends Table {
IntColumn get todo => integer()();
IntColumn get user => integer()();
@override
Set<Column> get primaryKey => {todo, user};
}
@UseSally(tables: [TodosTable, Categories, Users, SharedTodos])
class TodoDb extends _$TodoDb {
TodoDb(QueryExecutor e) : super(e);

View File

@ -64,10 +64,8 @@ class $TodosTableTable extends TodosTable
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get title => GeneratedTextColumn(
'title',
true,
);
GeneratedTextColumn get title =>
GeneratedTextColumn('title', true, minTextLength: 4, maxTextLength: 16);
@override
GeneratedTextColumn get content => GeneratedTextColumn(
'content',
@ -98,7 +96,7 @@ class $TodosTableTable extends TodosTable
targetDate.isAcceptableValue(instance.targetDate, isInserting) &&
category.isAcceptableValue(instance.category, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
Set<GeneratedColumn> get $primaryKey => null;
@override
TodoEntry map(Map<String, dynamic> data) {
return TodoEntry.fromData(data, _db);
@ -173,7 +171,7 @@ class $CategoriesTable extends Categories
id.isAcceptableValue(instance.id, isInserting) &&
description.isAcceptableValue(instance.description, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
Set<GeneratedColumn> get $primaryKey => null;
@override
Category map(Map<String, dynamic> data) {
return Category.fromData(data, _db);
@ -231,10 +229,8 @@ class $UsersTable extends Users implements TableInfo<Users, User> {
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get name => GeneratedTextColumn(
'name',
false,
);
GeneratedTextColumn get name =>
GeneratedTextColumn('name', false, minTextLength: 6, maxTextLength: 32);
@override
GeneratedBoolColumn get isAwesome => GeneratedBoolColumn(
'is_awesome',
@ -252,7 +248,7 @@ class $UsersTable extends Users implements TableInfo<Users, User> {
name.isAcceptableValue(instance.name, isInserting) &&
isAwesome.isAcceptableValue(instance.isAwesome, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
Set<GeneratedColumn> get $primaryKey => null;
@override
User map(Map<String, dynamic> data) {
return User.fromData(data, _db);
@ -274,11 +270,79 @@ class $UsersTable extends Users implements TableInfo<Users, User> {
}
}
class SharedTodo {
final int todo;
final int user;
SharedTodo({this.todo, this.user});
factory SharedTodo.fromData(Map<String, dynamic> data, GeneratedDatabase db) {
final intType = db.typeSystem.forDartType<int>();
return SharedTodo(
todo: intType.mapFromDatabaseResponse(data['todo']),
user: intType.mapFromDatabaseResponse(data['user']),
);
}
SharedTodo copyWith({int todo, int user}) => SharedTodo(
todo: todo ?? this.todo,
user: user ?? this.user,
);
@override
int get hashCode => (todo.hashCode) * 31 + user.hashCode;
@override
bool operator ==(other) =>
identical(this, other) ||
(other is SharedTodo && other.todo == todo && other.user == user);
}
class $SharedTodosTable extends SharedTodos
implements TableInfo<SharedTodos, SharedTodo> {
final GeneratedDatabase _db;
$SharedTodosTable(this._db);
@override
GeneratedIntColumn get todo => GeneratedIntColumn(
'todo',
false,
);
@override
GeneratedIntColumn get user => GeneratedIntColumn(
'user',
false,
);
@override
List<GeneratedColumn> get $columns => [todo, user];
@override
SharedTodos get asDslTable => this;
@override
String get $tableName => 'shared_todos';
@override
bool validateIntegrity(SharedTodo instance, bool isInserting) =>
todo.isAcceptableValue(instance.todo, isInserting) &&
user.isAcceptableValue(instance.user, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => {todo, user};
@override
SharedTodo map(Map<String, dynamic> data) {
return SharedTodo.fromData(data, _db);
}
@override
Map<String, Variable> entityToSql(SharedTodo d) {
final map = <String, Variable>{};
if (d.todo != null) {
map['todo'] = Variable<int, IntType>(d.todo);
}
if (d.user != null) {
map['user'] = Variable<int, IntType>(d.user);
}
return map;
}
}
abstract class _$TodoDb extends GeneratedDatabase {
_$TodoDb(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
$TodosTableTable get todosTable => $TodosTableTable(this);
$CategoriesTable get categories => $CategoriesTable(this);
$UsersTable get users => $UsersTable(this);
$SharedTodosTable get sharedTodos => $SharedTodosTable(this);
@override
List<TableInfo> get allTables => [todosTable, categories, users];
List<TableInfo> get allTables => [todosTable, categories, users, sharedTodos];
}

View File

@ -17,7 +17,7 @@ void main() {
test('creates all tables', () async {
await Migrator(db, mockQueryExecutor).createAllTables();
// should create todos, categories and users table
// should create todos, categories, users and shared_todos table
verify(mockQueryExecutor.call('CREATE TABLE IF NOT EXISTS todos '
'(id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR NULL, '
'content VARCHAR NOT NULL, target_date INTEGER NULL, '
@ -29,6 +29,9 @@ void main() {
verify(mockQueryExecutor.call('CREATE TABLE IF NOT EXISTS users '
'(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR NOT NULL, '
'is_awesome BOOLEAN NOT NULL CHECK (is_awesome in (0, 1)));'));
verify(mockQueryExecutor.call('CREATE TABLE IF NOT EXISTS shared_todos '
'(todo INTEGER NOT NULL, user INTEGER NOT NULL, PRIMARY KEY (todo, user));'));
});
test('creates individual tables', () async {

View File

@ -293,7 +293,7 @@ Please note that a workaround for most on this list exists with custom statement
### Planned for the future
These aren't sorted by priority. If you have more ideas or want some features happening soon,
let us know by creating an issue!
- Specify primary keys
- Specify custom primary keys ✔️
- Support an simplified update that doesn't need an explicit where based on the primary key
- Simple `COUNT(*)` operations (group operations will be much more complicated)
- Support default values and expressions

View File

@ -21,6 +21,7 @@ class FlutterQueryExecutor extends QueryExecutor {
final bool logStatements;
Database _db;
bool _hadMigration = false;
FlutterQueryExecutor({@required this.path, this.logStatements})
: _inDbPath = false;
@ -46,14 +47,25 @@ class FlutterQueryExecutor extends QueryExecutor {
resolvedPath,
version: databaseInfo.schemaVersion,
onCreate: (db, version) {
_hadMigration = true;
return databaseInfo.handleDatabaseCreation(
executor: (sql) => db.execute(sql),
);
},
onUpgrade: (db, from, to) {
_hadMigration = true;
return databaseInfo.handleDatabaseVersionChange(
executor: (sql) => db.execute(sql), from: from, to: to);
},
onOpen: (db) async {
_db = db;
// the openDatabase future will resolve later, so we can get an instance
// where we can send the queries from the onFinished operation;
final fn = databaseInfo.migration.onFinished;
if (fn != null && _hadMigration) {
await fn();
}
}
);
return true;

View File

@ -17,7 +17,11 @@ class SpecifiedTable {
final Set<SpecifiedColumn> primaryKey;
const SpecifiedTable(
{this.fromClass, this.columns, this.sqlName, this.dartTypeName, this.primaryKey});
{this.fromClass,
this.columns,
this.sqlName,
this.dartTypeName,
this.primaryKey});
}
String tableInfoNameForTableClass(ClassElement fromClass) =>

View File

@ -68,34 +68,47 @@ class TableParser extends ParserBase {
return tableName;
}
Set<SpecifiedColumn> _readPrimaryKey(ClassElement element, List<SpecifiedColumn> columns) {
Set<SpecifiedColumn> _readPrimaryKey(
ClassElement element, List<SpecifiedColumn> columns) {
final primaryKeyGetter = element.getGetter('primaryKey');
if (primaryKeyGetter == null) {
return null;
}
final ast = generator.loadElementDeclaration(primaryKeyGetter).node as MethodDeclaration;
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!'));
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.
// set expressions {x, y} are sometimes parsed as map literals whose values
// are an empty identifier {x: , y: }, but sometimes as proper set literal.
// this is probably due to backwards compatibility.
// 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 = <SpecifiedColumn>{};
for (var entry in mapLiteral.entries) {
final key = entry.key as Identifier;
final column = columns.singleWhere((column) => column.dartGetterName == key.name);
parsedPrimaryKey.add(column);
if (expression is MapLiteral) {
for (var entry in expression.entries) {
final key = entry.key as Identifier;
final column =
columns.singleWhere((column) => column.dartGetterName == key.name);
parsedPrimaryKey.add(column);
}
} else if (expression is SetLiteral) {
for (var entry in expression.elements) {
final column = columns.singleWhere(
(column) => column.dartGetterName == (entry as Identifier).name);
parsedPrimaryKey.add(column);
}
} else {
generator.errors.add(SallyError(
affectedElement: primaryKeyGetter,
message: 'This must return a set literal!'));
return null;
}
return parsedPrimaryKey;

View File

@ -13,7 +13,7 @@ import 'package:sally_generator/src/writer/database_writer.dart';
import 'package:source_gen/source_gen.dart';
class SallyGenerator extends GeneratorForAnnotation<UseSally> {
final Map<String, ParsedLibraryResult> _astForLibs = {};
//final Map<String, ParsedLibraryResult> _astForLibs = {};
final ErrorStore errors = ErrorStore();
TableParser tableParser;
@ -24,11 +24,12 @@ class SallyGenerator extends GeneratorForAnnotation<UseSally> {
final Map<DartType, SpecifiedTable> _foundTables = {};
ElementDeclarationResult loadElementDeclaration(Element element) {
final result = _astForLibs.putIfAbsent(element.library.name, () {
/*final result = _astForLibs.putIfAbsent(element.library.name, () {
// ignore: deprecated_member_use
return ParsedLibraryResultImpl.tmp(element.library);
});
});*/
// ignore: deprecated_member_use
final result = ParsedLibraryResultImpl.tmp(element.library);
return result.getElementDeclaration(element);
}
@ -61,6 +62,20 @@ class SallyGenerator extends GeneratorForAnnotation<UseSally> {
}
}
if (errors.errors.isNotEmpty) {
print('Warning: There were some errors whily running sally_generator:');
for (var error in errors.errors) {
print(error.message);
if (error.affectedElement != null) {
final span = spanForElement(error.affectedElement);
print('${span.start.toolString}\n${span.highlight()}');
}
}
errors.errors.clear();
}
if (_foundTables.isEmpty) return '';
final specifiedDb =

View File

@ -44,14 +44,7 @@ class TableWriter {
..write('@override\nString get \$tableName => \'${table.sqlName}\';\n');
_writeValidityCheckMethod(buffer);
// write primary key getter: Set<Column> get $primaryKey => <GeneratedColumn>{id};
final primaryKeyColumns = table.primaryKey.map((c) => c.dartGetterName);
buffer
..write(
'@override\nSet<GeneratedColumn> get \$primaryKey => <GeneratedColumn>{')
..write(primaryKeyColumns.join(', '))
..write('};\n');
_writePrimaryKeyOverride(buffer);
_writeMappingMethod(buffer);
_writeReverseMappingMethod(buffer);
@ -91,8 +84,17 @@ class TableWriter {
final isNullable = column.nullable;
final additionalParams = <String, String>{};
if (column.hasAI) {
additionalParams['hasAutoIncrement'] = 'true';
for (var feature in column.features) {
if (feature is AutoIncrement) {
additionalParams['hasAutoIncrement'] = 'true';
} else if (feature is LimitingTextLength) {
if (feature.minLength != null) {
additionalParams['minTextLength'] = feature.minLength.toString();
}
if (feature.maxLength != null) {
additionalParams['maxTextLength'] = feature.maxLength.toString();
}
}
}
// @override
@ -133,4 +135,24 @@ class TableWriter {
buffer..write(validationCode)..write(';\n');
}
void _writePrimaryKeyOverride(StringBuffer buffer) {
buffer.write('@override\nSet<GeneratedColumn> get \$primaryKey => ');
if (table.primaryKey == null) {
buffer.write('null;');
return;
}
buffer.write('{');
final pkList = table.primaryKey.toList();
for (var i = 0; i < pkList.length; i++) {
final pk = pkList[i];
buffer.write(pk.dartGetterName);
if (i != pkList.length - 1) {
buffer.write(', ');
}
}
buffer.write('};\n');
}
}

View File

@ -112,8 +112,10 @@ void main() async {
});
test('parses custom primary keys', () {
final table = TableParser(generator).parse(testLib.getType('CustomPrimaryKey'));
final table =
TableParser(generator).parse(testLib.getType('CustomPrimaryKey'));
expect(table.primaryKey, containsAll(table.columns));
expect(table.columns.any((column) => column.hasAI), isFalse);
});
}