Start to work on MySQL example

This commit is contained in:
Simon Binder 2019-07-23 22:16:21 +02:00
parent e46a8d84d5
commit 5f8ab42189
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 388 additions and 1 deletions

View File

@ -2,7 +2,21 @@
layout: guide
title: Custom databases
nav_order: 8
since: 2.0
permalink: /custom_backend
---
TBD
_Note_: This feature is available starting from Moor `2.0`.
# Custom databases
Moor has builtin support for Flutter using the `sqflite` package - it also supports the [web]({{"web" | absolute_url}})
and desktop apps. However, you can also use moor with a different database of your choice. In this guide, you'll learn how
to use Moor with a `mysql` database running on a server and with an encrypted database on a mobile device!
## MySQL
We'll connect to a MySQL server with the `sqljockey5` library, so let's first add that library to the `pubspec.yaml`:
```yaml
dependencies:
// you'll also need moor, of course
sqljocky5: ^2.2.0
```

11
examples/mysql/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Files and directories created by pub
.dart_tool/
.packages
# Remove the following pattern if you wish to check in your lock file
pubspec.lock
# Conventional directory for build outputs
build/
# Directory created by dartdoc
doc/api/

1
examples/mysql/README.md Normal file
View File

@ -0,0 +1 @@
Example that uses moor with an mysql server.

View File

@ -0,0 +1,21 @@
version: "3.7"
services:
database:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: example
ports:
- "3306:3306"
phpmyadmin:
depends_on:
- database
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: database
PMA_USER: root
PMA_PASSWORD: password
PMA_PORT: 3306

View File

@ -0,0 +1,48 @@
import 'package:moor/moor.dart';
import 'package:sqljocky5/connection/settings.dart';
import 'mysql.dart';
part 'database.g.dart';
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
@UseMoor(tables: [Users])
class Database extends _$Database {
Database._(QueryExecutor e) : super(e);
factory Database() {
final settings = ConnectionSettings(
user: 'root',
password: 'password',
host: 'localhost',
port: 3306,
db: 'example',
);
return Database._(MySqlBackend(settings));
}
@override
int get schemaVersion => 1;
@override
MigrationStrategy get migration {
return MigrationStrategy(
beforeOpen: (engine, details) async {
// we don't have migrations in mysql, so let's run them manually here!
final migrator = Migrator(this, engine.customStatement);
// will emit a "IF NOT EXISTS" statement, so its safe to run this
// on every open
await migrator.createAllTables();
},
);
}
Future<void> insertUser(String name) async {
await into(users).insert(UsersCompanion(name: Value(name)));
}
}

View File

@ -0,0 +1,160 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// **************************************************************************
// MoorGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps
class User extends DataClass implements Insertable<User> {
final int id;
final String name;
User({@required this.id, @required this.name});
factory User.fromData(Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) {
final effectivePrefix = prefix ?? '';
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
return User(
id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id']),
name: stringType.mapFromDatabaseResponse(data['${effectivePrefix}name']),
);
}
factory User.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer = const ValueSerializer.defaults()}) {
return User(
id: serializer.fromJson<int>(json['id']),
name: serializer.fromJson<String>(json['name']),
);
}
@override
Map<String, dynamic> toJson(
{ValueSerializer serializer = const ValueSerializer.defaults()}) {
return {
'id': serializer.toJson<int>(id),
'name': serializer.toJson<String>(name),
};
}
@override
T createCompanion<T extends UpdateCompanion<User>>(bool nullToAbsent) {
return UsersCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value(id),
name: name == null && nullToAbsent ? const Value.absent() : Value(name),
) as T;
}
User copyWith({int id, String name}) => User(
id: id ?? this.id,
name: name ?? this.name,
);
@override
String toString() {
return (StringBuffer('User(')
..write('id: $id, ')
..write('name: $name')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), name.hashCode));
@override
bool operator ==(other) =>
identical(this, other) ||
(other is User && other.id == id && other.name == name);
}
class UsersCompanion extends UpdateCompanion<User> {
final Value<int> id;
final Value<String> name;
const UsersCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
});
}
class $UsersTable extends Users with TableInfo<$UsersTable, User> {
final GeneratedDatabase _db;
final String _alias;
$UsersTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
GeneratedIntColumn _id;
@override
GeneratedIntColumn get id => _id ??= _constructId();
GeneratedIntColumn _constructId() {
return GeneratedIntColumn('id', $tableName, false, hasAutoIncrement: true);
}
final VerificationMeta _nameMeta = const VerificationMeta('name');
GeneratedTextColumn _name;
@override
GeneratedTextColumn get name => _name ??= _constructName();
GeneratedTextColumn _constructName() {
return GeneratedTextColumn(
'name',
$tableName,
false,
);
}
@override
List<GeneratedColumn> get $columns => [id, name];
@override
$UsersTable get asDslTable => this;
@override
String get $tableName => _alias ?? 'users';
@override
final String actualTableName = 'users';
@override
VerificationContext validateIntegrity(UsersCompanion d,
{bool isInserting = false}) {
final context = VerificationContext();
if (d.id.present) {
context.handle(_idMeta, id.isAcceptableValue(d.id.value, _idMeta));
} else if (id.isRequired && isInserting) {
context.missing(_idMeta);
}
if (d.name.present) {
context.handle(
_nameMeta, name.isAcceptableValue(d.name.value, _nameMeta));
} else if (name.isRequired && isInserting) {
context.missing(_nameMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
User map(Map<String, dynamic> data, {String tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
return User.fromData(data, _db, prefix: effectivePrefix);
}
@override
Map<String, Variable> entityToSql(UsersCompanion d) {
final map = <String, Variable>{};
if (d.id.present) {
map['id'] = Variable<int, IntType>(d.id.value);
}
if (d.name.present) {
map['name'] = Variable<String, StringType>(d.name.value);
}
return map;
}
@override
$UsersTable createAlias(String alias) {
return $UsersTable(_db, alias);
}
}
abstract class _$Database extends GeneratedDatabase {
_$Database(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
$UsersTable _users;
$UsersTable get users => _users ??= $UsersTable(this);
@override
List<TableInfo> get allTables => [users];
}

View File

@ -0,0 +1,99 @@
import 'package:moor/backends.dart';
import 'package:moor/moor.dart';
import 'package:sqljocky5/connection/connection.dart';
import 'package:sqljocky5/sqljocky.dart';
class MySqlBackend extends DelegatedDatabase {
MySqlBackend(ConnectionSettings settings)
: super(_MySqlDelegate(settings), logStatements: true);
}
mixin _MySqlExecutor on QueryDelegate {
Querier get _querier;
@override
Future<void> runBatched(List<BatchedStatement> statements) async {
for (var stmt in statements) {
await _querier.preparedMulti(stmt.sql, stmt.variables);
}
}
@override
Future<void> runCustom(String statement, List args) async {
await _querier.prepared(statement, args);
}
@override
Future<int> runInsert(String statement, List args) async {
final result = await _querier.prepared(statement, args);
return result.insertId;
}
@override
Future<QueryResult> runSelect(String statement, List args) async {
final result = await _querier.prepared(statement, args);
final columns = [for (var field in result.fields) field.name];
final rows = result.toList();
return QueryResult(columns, rows);
}
@override
Future<int> runUpdate(String statement, List args) async {
final result = await _querier.prepared(statement, args);
return result.affectedRows;
}
}
class _MySqlDelegate extends DatabaseDelegate with _MySqlExecutor {
final ConnectionSettings _settings;
MySqlConnection _connection;
_MySqlDelegate(this._settings);
@override
Future<bool> get isOpen async => _connection != null;
@override
Querier get _querier => _connection;
@override
final DbVersionDelegate versionDelegate = const NoVersionDelegate();
@override
TransactionDelegate get transactionDelegate => _TransactionOpener(this);
@override
Future<void> open([GeneratedDatabase db]) async {
_connection = await MySqlConnection.connect(_settings);
}
@override
Future<void> close() async {
await _connection.close();
}
}
class _TransactionOpener extends SupportedTransactionDelegate {
final _MySqlDelegate _delegate;
_TransactionOpener(this._delegate);
@override
void startTransaction(Future Function(QueryDelegate) run) {
_delegate._connection.transaction((transaction) async {
final executor = _TransactionExecutor(transaction);
await run(executor);
});
}
}
class _TransactionExecutor extends QueryDelegate with _MySqlExecutor {
@override
final Querier _querier;
_TransactionExecutor(this._querier);
}

6
examples/mysql/main.dart Normal file
View File

@ -0,0 +1,6 @@
import 'lib/database.dart';
void main() async {
final database = Database();
await database.insertUser('MySql test');
}

View File

@ -0,0 +1,21 @@
name: mysql
description: A sample command-line application.
# version: 1.0.0
# homepage: https://www.example.com
# author: Simon Binder <oss@simonbinder.eu>
environment:
sdk: '>=2.4.0 <3.0.0'
dependencies:
moor: ^1.6.0
sqljocky5: ^2.2.0
dev_dependencies:
test: ^1.6.0
moor_generator: ^1.6.0
build_runner:
dependency_overrides:
moor:
path: ../../moor

View File

@ -42,6 +42,12 @@ abstract class DatabaseDelegate implements QueryDelegate {
/// [UseMoor]. It might be useful to read the [GeneratedDatabase.schemaVersion]
/// if that information is required while opening the database.
Future<void> open([GeneratedDatabase db]);
/// Closes this database. When the future completes, all resources used
/// by this database should have been disposed.
Future<void> close() async {
// default no-op implementation
}
}
/// An interface which can execute sql statements.