Skip to content

Commit

Permalink
Add support for mysql driver (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
necessarylion authored Jan 2, 2024
1 parent c130808 commit f3f64e9
Show file tree
Hide file tree
Showing 31 changed files with 389 additions and 86 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ jobs:
runs-on: ubuntu-latest

services:

mariadb:
image: mariadb:latest
ports:
- 3306:3306
env:
MYSQL_USER: dox
MYSQL_PASSWORD: password
MYSQL_DATABASE: dox-framework
MYSQL_ROOT_PASSWORD: password
options: >-
--health-cmd="healthcheck.sh
--connect
--innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=3
postgres:
image: postgres:latest
env:
Expand Down Expand Up @@ -42,4 +60,8 @@ jobs:
- name: Melos Bootstrap
run: melos bs
- name: Run Tests
run: melos test
run: melos test --no-select
- name: Run Test (query builder postgres)
run: melos test_query_builder_postgres
- name: Run Test (query builder mysql)
run: DB_USER=dox melos test_query_builder_mysql
17 changes: 16 additions & 1 deletion melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,20 @@ packages:
- packages/**

scripts:

test:
exec: dart test --concurrency=1
exec: dart test --concurrency=1
packageFilters:
ignore: "dox_query_builder"

test_query_builder_postgres:
exec: DRIVER=postgres dart test --concurrency=1
packageFilters:
noSelect: true
scope: "dox_query_builder"

test_query_builder_mysql:
exec: DRIVER=mysql dart test --concurrency=1
packageFilters:
noSelect: true
scope: "dox_query_builder"
8 changes: 8 additions & 0 deletions packages/dox-app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mysql1:
dependency: transitive
description:
name: mysql1
sha256: "68aec7003d2abc85769bafa1777af3f4a390a90c31032b89636758ff8eb839e9"
url: "https://pub.dev"
source: hosted
version: "0.20.0"
node_preamble:
dependency: transitive
description:
Expand Down
13 changes: 9 additions & 4 deletions packages/dox-query-builder/lib/src/drivers/db_driver.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import 'package:postgres/postgres.dart';
enum Driver { postgres, mysql }

/// interface for database driver
abstract class DBDriver {
Driver getName();

/// run query and return map result
Future<Result> execute(String query,
Future<T> execute<T>(String query,
{Map<String, dynamic>? substitutionValues});

/// run query and return map result
Future<List<Map<String, dynamic>>> mappedResultsQuery(String query,
{Map<String, dynamic>? substitutionValues});
Future<List<Map<String, dynamic>>> mappedResultsQuery(
String query, {
String? primaryKey,
Map<String, dynamic>? substitutionValues,
});

/// run query, this function do not return any value
Future<void> query(String query, {Map<String, dynamic>? substitutionValues});
Expand Down
136 changes: 136 additions & 0 deletions packages/dox-query-builder/lib/src/drivers/mysql_driver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import 'dart:convert';

import 'package:dox_query_builder/dox_query_builder.dart';
import 'package:mysql1/mysql1.dart';

/// driver for postgres SQL
/// support PostgreSQLConnection and PgPool
class MysqlDriver extends DBDriver {
final MySqlConnection conn;

/// constructor
MysqlDriver({required this.conn});

@override
Driver getName() {
return Driver.mysql;
}

/// run query and return map result
@override
Future<T> execute<T>(
String query, {
Map<String, dynamic>? substitutionValues,
}) async {
dynamic result =
await conn.run(query, substitutionValues: substitutionValues);
return result as T;
}

/// run query and return map result
@override
Future<List<Map<String, dynamic>>> mappedResultsQuery(
String query, {
String? primaryKey,
Map<String, dynamic>? substitutionValues,
}) async {
return await conn.execute(
query,
primaryKey: primaryKey,
substitutionValues: substitutionValues,
);
}

/// only run query
@override
Future<void> query(
String query, {
Map<String, dynamic>? substitutionValues,
}) async {
await conn.run(query, substitutionValues: substitutionValues);
}
}

/// extension on mysql connection
extension MySqlConnectionExecute on MySqlConnection {
Future<List<Map<String, dynamic>>> execute(
String sql, {
String? primaryKey,
Map<String, dynamic>? substitutionValues,
}) async {
Results results = await run(sql, substitutionValues: substitutionValues);

/// if result has last insert id, it mean insert query and need to return
/// correct id
if (results.insertId != null &&
results.insertId! > 0 &&
primaryKey != null) {
return <Map<String, dynamic>>[
<String, dynamic>{primaryKey: results.insertId}
];
}

/// normal get query which need to return list of map data
/// similar to postgres
return _parseResultToMap(results);
}

Future<Results> run(
String sql, {
Map<String, dynamic>? substitutionValues,
}) async {
List<Object> params = _substitutionValuesToList(substitutionValues);
sql = sql.replaceAll(RegExp(r'@\w+'), '?');
return await query(sql, params);
}

List<Map<String, dynamic>> _parseResultToMap(Results results) {
List<Map<String, dynamic>> result = <Map<String, dynamic>>[];
for (ResultRow element in results) {
Map<String, dynamic> data = <String, dynamic>{};
element.fields.forEach((String key, dynamic value) {
/// blog need to convert to string
if (value is Blob) {
data[key] = value.toString();
} else {
data[key] = value;
}

/// if data is json string
if (data[key].toString().startsWith('{"')) {
try {
/// convert to map
data[key] = jsonDecode(data[key]);
} catch (error) {
/// ignore error
}
}
});
result.add(data);
}
return result;
}

List<String> _substitutionValuesToList(dynamic params) {
List<String> list = <String>[];
if (params != null && (params as Map<String, dynamic>).isNotEmpty) {
params.forEach((String key, dynamic value) {
if (value is Map) {
value = jsonEncode(value);
} else if (value is DateTime) {
value = value.toIso8601String().split('.').first;
} else if (value.toString().endsWith('Z')) {
try {
value = DateTime.parse(value).toIso8601String().split('.').first;
} catch (error) {
value = value.toString();
}
} else {
value = value.toString();
}
list.add(value);
});
}
return list;
}
}
10 changes: 8 additions & 2 deletions packages/dox-query-builder/lib/src/drivers/postgres_driver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@ class PostgresDriver extends DBDriver {
/// constructor
PostgresDriver({required this.conn});

@override
Driver getName() {
return Driver.postgres;
}

/// run query and return map result
@override
Future<Result> execute(
Future<T> execute<T>(
String query, {
Map<String, dynamic>? substitutionValues,
}) async {
Result result = await conn.runTx((TxSession s) async {
return await s.execute(Sql.named(query), parameters: substitutionValues);
});
return result;
return result as T;
}

/// run query and return map result
@override
Future<List<Map<String, dynamic>>> mappedResultsQuery(
String query, {
String? primaryKey,
Map<String, dynamic>? substitutionValues,
}) async {
Result result =
Expand Down
18 changes: 13 additions & 5 deletions packages/dox-query-builder/lib/src/insert.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:dox_query_builder/dox_query_builder.dart';

import 'shared_mixin.dart';

mixin Insert<T> implements SharedMixin<T> {
Expand Down Expand Up @@ -29,11 +31,13 @@ mixin Insert<T> implements SharedMixin<T> {
await insertMultiple(<Map<String, dynamic>>[data]);
if (result.isNotEmpty) {
Map<String, dynamic> insertedData = result.first;
int id = insertedData[primaryKey] ?? 0;
int? id = insertedData[primaryKey];
resetSubstitutionValues();
return await queryBuilder.find(id);
if (id != null) {
return await queryBuilder.find(id);
}
}
return null;
return <String, dynamic>{};
}

/// insert/create multiple records
Expand All @@ -59,14 +63,18 @@ mixin Insert<T> implements SharedMixin<T> {
List<String> ret = <String>[];
data.forEach((String key, dynamic value) {
String columnKey = helper.parseColumnKey(key);
ret.add("@$columnKey");
ret.add(columnKey);
addSubstitutionValues(columnKey, value);
});
values.add("(${ret.join(',')})");
}

String returningQuery = queryBuilder.dbDriver.getName() == Driver.postgres
? 'RETURNING $primaryKey'
: '';

String query =
"INSERT INTO $tableName (${columns.join(',')}) VALUES ${values.join(',')} RETURNING $primaryKey";
"INSERT INTO $tableName (${columns.join(',')}) VALUES ${values.join(',')} $returningQuery";
return await helper.runQuery(query);
}
}
25 changes: 22 additions & 3 deletions packages/dox-query-builder/lib/src/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:dox_query_builder/dox_query_builder.dart';
import 'package:dox_query_builder/src/drivers/mysql_driver.dart';
import 'package:mysql1/mysql1.dart';
import 'package:postgres/postgres.dart';

class SqlQueryBuilder {
Expand All @@ -10,7 +12,7 @@ class SqlQueryBuilder {

SqlQueryBuilder._internal();

late DBDriver db;
late DBDriver dbDriver;

bool debug = true;

Expand All @@ -25,12 +27,29 @@ class SqlQueryBuilder {
/// );
/// ```
static void initialize({
required Connection database,
required dynamic database,
bool debug = false,
QueryPrinter? printer,
Driver driver = Driver.postgres,
}) {
SqlQueryBuilder sql = SqlQueryBuilder();
sql.db = PostgresDriver(conn: database);

if (driver == Driver.postgres) {
if (database is! Connection) {
throw Exception(
'Invalid database connection. It must be postgres `Connection` type');
}
sql.dbDriver = PostgresDriver(conn: database);
} else if (driver == Driver.mysql) {
if (database is! MySqlConnection) {
throw Exception(
'Invalid database connection. It must be `MySqlConnection` type');
}
sql.dbDriver = MysqlDriver(conn: database);
} else {
throw Exception('Invalid driver or not supported');
}

sql.debug = debug;
// coverage:ignore-start
if (printer != null) {
Expand Down
7 changes: 4 additions & 3 deletions packages/dox-query-builder/lib/src/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@ class Model<T> extends QueryBuilder<T> {
String? updatedAtColumn = timestampsColumn['updated_at'];

Map<String, dynamic> values = toMap(removeRelations: true);
DateTime currentTime = now();
if (values[primaryKey] == null) {
values.removeWhere((String key, dynamic value) => value == null);

if (createdAtColumn != null) {
values[createdAtColumn] = now();
values[createdAtColumn] = currentTime;
createdAt = values[createdAtColumn];
}

if (updatedAtColumn != null) {
values[updatedAtColumn] = now();
values[updatedAtColumn] = currentTime;
updatedAt = values[updatedAtColumn];
}

Expand All @@ -68,7 +69,7 @@ class Model<T> extends QueryBuilder<T> {
values.remove(createdAtColumn);
values.removeWhere((String key, dynamic value) => value == null);
if (updatedAtColumn != null) {
values[updatedAtColumn] = now();
values[updatedAtColumn] = currentTime;
updatedAt = values[updatedAtColumn];
}

Expand Down
Loading

0 comments on commit f3f64e9

Please sign in to comment.