From: Hamatoma Date: Fri, 16 Oct 2020 11:21:47 +0000 (+0200) Subject: daily work: sqlExportBackend() works, improvements all models X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=a32cd42489526c807e7bba840fa25f609c30a934;p=flutter_bones.git daily work: sqlExportBackend() works, improvements all models --- diff --git a/lib/db_tool.dart b/lib/db_tool.dart new file mode 100644 index 0000000..7c4c080 --- /dev/null +++ b/lib/db_tool.dart @@ -0,0 +1,89 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter_bones/flutter_bones.dart'; + +import 'src/model/standard/role_model.dart'; +import 'src/model/standard/user_model.dart'; + +void main(List argv) async { + final logger = MemoryLogger(LEVEL_FINE); + final dbHelper = DbHelper(logger); + if (argv.isEmpty) { + argv = ['export-sql']; + } + final mode = argv[0]; + argv.removeAt(0); + final options = []; + argv = StringHelper.splitArgv(argv, options); + switch (mode) { + case 'export-sql': + dbHelper.exportSql(argv, options); + break; + default: + logger.error('unknown mode: $mode'); + break; + } +} + +class DbHelper { + final BaseLogger logger; + + DbHelper(this.logger); + + void exportSql(List args, List options) { + String directory; + String value; + for (var opt in options) { + value = StringUtils.stringOption('directory', 'd', opt); + if (value != null) { + directory = value; + } + } + directory ??= 'data'; + final dirDDL = FileSync.joinPaths(directory, 'ddl'); + final dirREST = FileSync.joinPaths(directory, 'rest'); + FileSync.ensureDirectory(dirDDL); + FileSync.ensureDirectory(dirREST); + if (args.isEmpty) { + args = ['user', 'role']; + } + while (args.isNotEmpty) { + final name = args[0]; + args.removeAt(0); + ModuleModel module; + switch (name) { + case 'user': + module = UserModel(logger); + break; + case 'role': + module = RoleModel(logger); + break; + default: + logger.error('unknown table'); + break; + } + if (module != null) { + module.parse(); + var filename = FileSync.joinPaths(dirDDL, '$name.sql'); + FileSync.toFile(filename, module.exportSqlCreateTable()); + print('exported: $filename'); + filename = FileSync.joinPaths(dirREST, '$name.yaml'); + FileSync.toFile(filename, module.exportSqlBackend()); + print('exported: $filename'); + } + } + } + + void usage(String error) { + print('''usage: dbhelper [] +: + create-sql [] [] + : 'role', 'user' ... + : + -d or --directory=: + the base directory used for the exported files. +Examples: +dbhelper create-sql role role.sql -d/tmp +dbhelper create-sql --directory=/opt/sql-data +'''); + } +} diff --git a/lib/main.dart b/lib/main.dart index 7f57697..29a9d9e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bones/app.dart'; -void main() { +void main(List args) { + //args.length > 1 && args[0].startsWith('--') && runApp(BoneApp()); } - diff --git a/lib/src/helper/string_helper.dart b/lib/src/helper/string_helper.dart index 35ce393..8559759 100644 --- a/lib/src/helper/string_helper.dart +++ b/lib/src/helper/string_helper.dart @@ -1,9 +1,45 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter_bones/flutter_bones.dart'; +import 'package:intl/intl.dart'; class StringHelper { static final regExpTrue = RegExp(r'^(true|yes)$', caseSensitive: false); static final regExpFalse = RegExp(r'^(false|no)$', caseSensitive: false); + static const locale = 'de_DE'; + + /// Converts a [dateTime] into a string. + /// If [dateTime] is null the current date and time is used. + /// If [sortable] is true the result is sortable (year.month.day ...). + /// [separator] is the part between date and time. + /// If [withSeconds] is true the result contains the seconds otherwise not. + static dateTimeToString(DateTime dateTime, + {String separator = ' ', withSeconds: true, sortable = false}) { + dateTime ??= DateTime.now(); + String rc; + if (sortable) { + rc = DateFormat(withSeconds + ? 'yyyy.MM.dd${separator}HH:mm:ss' + : 'yyyy.MM.dd${separator}HH:mm') + .format(dateTime); + } else { + rc = dateToString(dateTime) + + separator + + (withSeconds + ? DateFormat.Hms().format(dateTime) + : DateFormat.Hm(locale).format(dateTime)); + } + return rc; + } + + /// Converts a [date] into a string. + /// If [date] is null the current date is used. + /// If [sortable] is true the result is sortable (year.month.day). + static dateToString(DateTime date, {sortable = false}) { + date ??= DateTime.now(); + final formatter = + sortable ? DateFormat('yyyy.MM.dd') : DateFormat.yMd(locale); + return formatter.format(date); + } /// Converts a string to a given [dataType]. static dynamic fromString(String value, DataType dataType) { @@ -43,4 +79,20 @@ class StringHelper { } return rc; } + + /// Splits a argument list into an [option] list and a true arguments list. + /// [args]: the argument list to split. + /// [options]: OUT: the options list + /// Returns the true argument list (arguments without options). + static List splitArgv(List args, List options) { + final rc = []; + for (var arg in args) { + if (arg.startsWith('-')) { + options.add(arg); + } else { + rc.add(arg); + } + } + return rc; + } } diff --git a/lib/src/model/column_model.dart b/lib/src/model/column_model.dart index f9a3467..63ee2f8 100644 --- a/lib/src/model/column_model.dart +++ b/lib/src/model/column_model.dart @@ -1,4 +1,5 @@ import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/cupertino.dart'; import 'model_base.dart'; import 'model_types.dart'; @@ -8,7 +9,7 @@ import 'widget_model.dart'; /// Describes a column of a database table. class ColumnModel extends WidgetModel { static final regExprOptions = RegExp( - r'^(undef|readonly|disabled|hidden|null|notnull|primary|required|unique)$'); + r'^(undef|readonly|disabled|doStore|hidden|null|notnull|password|primary|required|unique)$'); String name; String label; String toolTip; @@ -20,9 +21,22 @@ class ColumnModel extends WidgetModel { final Map map; String foreignKey; + /// A constructor used in the parser. ColumnModel(this.table, this.map, BaseLogger logger) : super(null, null, WidgetModelType.column, logger); + /// A constructor for columns created by the program. + ColumnModel.raw( + {@required this.name, + @required this.table, + @required this.label, + @required this.dataType, + @required this.options, + this.size, + this.map, + BaseLogger logger}) + : super(null, null, WidgetModelType.column, logger); + /// Dumps the instance into a [StringBuffer] StringBuffer dump(StringBuffer stringBuffer) { stringBuffer.write( @@ -42,20 +56,28 @@ class ColumnModel extends WidgetModel { /// Parses the map and stores the data in the instance. void parse() { - name = parseString('name', map, required: true); + name = parseString('column', map, required: true); checkSuperfluousAttributes( map, - 'dataType foreignKey label name options rows size tooTip widgetType' + 'column dataType foreignKey label options rows size tooTip widgetType' .split(' ')); dataType = parseEnum('dataType', map, DataType.values, required: true); label = parseString('label', map, required: false); toolTip = parseString('toolTip', map, required: false); size = parseInt('size', map, required: dataType == DataType.string); - rows = parseInt('size', map); + rows = parseInt('rows', map); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); + if (options.contains('primary')) { + if (!options.contains('notnull')) { + options.add('notnull'); + } + if (!options.contains('unique')) { + options.add('unique'); + } + } } @override diff --git a/lib/src/model/field_model.dart b/lib/src/model/field_model.dart index 4ddc9e0..d9185fb 100644 --- a/lib/src/model/field_model.dart +++ b/lib/src/model/field_model.dart @@ -17,6 +17,7 @@ abstract class FieldModel extends WidgetModel { DataType dataType; FormFieldSetter onSaved; List options; + FilterType filterType; final Map map; var _value; @@ -52,6 +53,7 @@ abstract class FieldModel extends WidgetModel { name = parseString('name', map, required: true); label = parseString('label', map, required: false); toolTip = parseString('toolTip', map, required: false); + filterType = parseEnum('filterType', map, FilterType.values); dataType = parseEnum('dataType', map, DataType.values); if (dataType == null) { diff --git a/lib/src/model/model_base.dart b/lib/src/model/model_base.dart index abccce1..189ccfe 100644 --- a/lib/src/model/model_base.dart +++ b/lib/src/model/model_base.dart @@ -74,20 +74,20 @@ abstract class ModelBase { if (!map.containsKey(key)) { if (required) { logger.error('missing int attribute "$key" in ${fullName()}'); - } else { - final value = map[key]; - if (value != null) { - if (value.runtimeType == int) { - rc = map[key]; - } else if (value.runtimeType == String) { - if (Validation.isInt(value)) { - rc = StringUtils.asInt(value); - } else { - logger.error('not an integer: $value map[$key] in {fullName()}'); - } + } + } else { + final value = map[key]; + if (value != null) { + if (value.runtimeType == int) { + rc = map[key]; + } else if (value.runtimeType == String) { + if (Validation.isInt(value)) { + rc = StringUtils.asInt(value); } else { logger.error('not an integer: $value map[$key] in {fullName()}'); } + } else { + logger.error('not an integer: $value map[$key] in {fullName()}'); } } } @@ -149,7 +149,7 @@ abstract class ModelBase { } /// Fetches an entry from a map addressed by a [key]. - /// This entry is splitted by the delimiter given at index 0. + /// This entry is split by the delimiter given at index 0. /// Example: ";a;b" returns ['a', 'b']. /// An error is logged if [required] is true and the map does not contain the key. List parseValueList( @@ -162,19 +162,21 @@ abstract class ModelBase { if (ModelBase.isList(map[key])) { rc = map[key]; } else { - final strings = parseStringList(key, map, required: required); - rc = strings.map((item) { - var rc2; - switch (dataType) { - case DataType.int: - rc2 = StringUtils.asInt(item); - break; - default: - logger.error('unknown dataType in parseValueList()'); - rc2 = item; - } - return rc2; - }); + final strings = parseStringList(key, map, required: required).toList(); + if (strings.isNotEmpty) { + rc = strings.map((item) { + var rc2; + switch (dataType) { + case DataType.int: + rc2 = StringUtils.asInt(item); + break; + default: + logger.error('unknown dataType in parseValueList()'); + rc2 = item; + } + return rc2; + }); + } } return rc; } diff --git a/lib/src/model/model_types.dart b/lib/src/model/model_types.dart index 292a9ec..4874a31 100644 --- a/lib/src/model/model_types.dart +++ b/lib/src/model/model_types.dart @@ -14,5 +14,6 @@ enum FilterType { dateTil, dateTimeFrom, dateTimeTil, + equals, pattern, } diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index 7763206..2d781e2 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -23,6 +23,21 @@ class ModuleModel extends ModelBase { ModuleModel(this.map, BaseLogger logger) : super(logger); + /// Adds the column [name] from [source] to [target] if that column is not member of [target]. + void addColumnIfMissing( + List target, List source, String name) { + final isEmpty = target.isEmpty; + final first = isEmpty + ? null + : target.firstWhere((col) => col.name == name, orElse: () => null); + final first2 = source.isEmpty + ? null + : source.firstWhere((col) => col.name == name, orElse: () => null); + if (first == null && first2 != null) { + target.add(first2); + } + } + /// Appends a [page] to the instance. void addPage(PageModel page) { if (pages.where((element) => element.name == page.name).isNotEmpty) { @@ -48,62 +63,188 @@ class ModuleModel extends ModelBase { return stringBuffer; } + /// Writes the insert SQL part into the [buffer]. + void exportInsert(StringBuffer buffer, TableModel table) { + buffer.write(''' sql: "INSERT INTO ${table.name}('''); + final columns = table.columns + .where((col) => + col.hasOption('doStore') || + (!col.hasOption('primary') && (!col.hasOption('hidden')))) + .toList(); + addColumnIfMissing(columns, table.columns, '${table.name}_createdat'); + addColumnIfMissing(columns, table.columns, '${table.name}_createdby'); + buffer.write(columns.fold( + '', + (prev, col) => + (prev as String) + + ((prev as String).isEmpty ? col.name : ',' + col.name))); + buffer.write(')\n'); + buffer.write(' VALUES('); + buffer.write(columns.fold( + '', + (prev, col) => + (prev as String) + + ((prev as String).isEmpty ? '' : ',') + + (col.name.endsWith('createdat') ? 'NOW()' : (':' + col.name)))); + buffer.write(');"\n'); + } + + /// Writes the list SQL part into the [buffer]. + void exportList(StringBuffer buffer, TableModel table) { + final page = pages + .firstWhere((element) => element.pageModelType == PageModelType.list, + orElse: () => null); + if (page != null) { + buffer.write(''' - name: list + type: list + sql: "SELECT * from ${table.name}\n'''); + // @ToDo: joins + final fields = page.fields.values + .where((item) => item is FieldModel && item.filterType != null); + if (fields.isEmpty) { + buffer.write(' WHERE 1;"'); + } else { + buffer.write(' WHERE '); + var first = true; + for (var field in fields) { + if (!first) { + buffer.write(' AND '); + } + switch (field.filterType) { + case FilterType.dateFrom: + case FilterType.dateTimeFrom: + buffer.write('${field.name}>=:${field.name}'); + break; + case FilterType.dateTil: + case FilterType.dateTimeTil: + buffer.write('${field.name}<=:${field.name}'); + break; + case FilterType.pattern: + buffer.write('${field.name} like :${field.name}'); + break; + } + first = false; + } + buffer.write(';"\n'); + } + } + } + + /// Exports a YAML file (as String) describing the SQL statements for insert, update ... + /// testDate: the fix date 2020.01.01 will be used (for unit tests) + String exportSqlBackend({bool testDate: false}) { + StringBuffer buffer = StringBuffer(); + final date = StringHelper.dateTimeToString( + testDate ? DateTime(2020, 1, 1) : null, + sortable: true); + final table = mainTable(); + if (table != null) { + buffer.write('''--- +# configuration of the bones backend for ${table.name}: +created: $date +author: flutter_bones.module_model.exportSqlBackend() +version: 1.0.0 +modules: + - module: ${table.name} + list: + - name: insert + type: insert +'''); + exportInsert(buffer, table); + buffer.write(''' - name: update + type: update +'''); + exportUpdate(buffer, table); + buffer.write(''' - name: delete + type: delete + sql: "DELETE from ${table.name} WHERE ${table.name}_id=:${table.name}_id;" + - name: record + type: record + sql: "SELECT * from ${table.name} WHERE ${table.name}_id=:${table.name}_id;" +'''); + exportList(buffer, table); + } + return buffer.toString(); + } + /// Exports the SQL statement to create the module tables. - String exportSql() { + String exportSqlCreateTable() { StringBuffer buffer = StringBuffer(); for (var table in tables) { - buffer.write('drop table if exists ${table.name};\n'); - buffer.write('create table ${table.name} (\n'); + buffer.write('DROP TABLE IF EXISTS ${table.name};\n'); + buffer.write('CREATE TABLE ${table.name} (\n'); for (var column in table.columns) { String type; switch (column.dataType) { case DataType.bool: - type = 'char(1)'; + type = 'CHAR(1)'; break; case DataType.currency: - type = 'int(10)'; + type = 'INT(10)'; break; case DataType.date: - type = 'date'; + type = 'DATE'; break; case DataType.dateTime: - type = 'timestamp'; + type = 'TIMESTAMP'; break; case DataType.float: - type = 'double'; + type = 'DOUBLE'; break; case DataType.int: - type = 'int(10)'; + type = 'INT(10)'; break; case DataType.reference: - type = 'int(10)'; + type = 'INT(10) UNSIGNED'; break; case DataType.string: if (column.size == null) { - type = 'varchar(255)'; + type = 'VARCHAR(255)'; } else if (column.size <= 255) { - type = 'varchar($column.size})'; + type = 'VARCHAR(${column.size})'; } else if (column.size <= 0xffff) { - type = 'text'; + type = 'TEXT'; } else { - type = 'mediumtext'; + type = 'MEDIUMTEXT'; } break; } String options = ''; for (var option in column.options) { - if ('notnull null primary unique'.contains(option)) { + if ('notnull null unique'.contains(option)) { options += ' ' + option; } } - buffer.write(' ${column.name} $type$options;\n'); + buffer.write(' ${column.name} $type$options,\n'); } + buffer.write(' PRIMARY KEY(${table.name}_id)\n'); buffer.write(');\n'); } final rc = buffer.toString(); return rc; } + /// Writes the insert SQL part into the [buffer]. + void exportUpdate(StringBuffer buffer, TableModel table) { + buffer.write(' sql: "UPDATE ${table.name} SET\n '); + final columns = table.columns + .where((col) => + col.hasOption('doStore') || + (!col.hasOption('primary') && !col.hasOption('hidden'))) + .toList(); + addColumnIfMissing(columns, table.columns, '${table.name}_changedat'); + addColumnIfMissing(columns, table.columns, '${table.name}_changedby'); + buffer.write(columns.fold( + '', + (prev, col) => + (prev as String) + + ((prev as String).isEmpty ? '' : ',') + + col.name + + '=' + + ((col.name.endsWith('changedat') ? 'NOW()' : ':' + col.name)))); + buffer.write('\n WHERE ${table.name}_id=:${table.name}_id";\n'); + } + /// Returns the name including the names of the parent @override String fullName() => name; @@ -115,7 +256,8 @@ class ModuleModel extends ModelBase { TableModel table = tables[0]; if (columnName.contains('.')) { final parts = columnName.split('.'); - table = tables.firstWhere((element) => element.name == parts[0]); + table = tables.firstWhere((element) => element.name == parts[0], + orElse: () => null); if (table == null) { logger.error( 'unknown reference (table) "$columnName" in ${caller.fullName()}'); @@ -131,13 +273,14 @@ class ModuleModel extends ModelBase { /// Returns the main table of the module. /// This is the first defined table. TableModel mainTable() { - TableModel rc = tables.length == 0 ? null : tables[0]; + final rc = tables.isEmpty ? null : tables[0]; return rc; } /// Returns a child page given by [name], null otherwise. PageModel pageByName(String name) { - final found = pages.firstWhere((element) => element.name == name); + final found = pages.firstWhere((element) => element.name == name, + orElse: () => null); return found; } @@ -165,7 +308,8 @@ class ModuleModel extends ModelBase { /// Returns a child table given by [name], null otherwise. TableModel tableByName(String name) { - final found = tables.firstWhere((element) => element.name == name); + final found = tables.firstWhere((element) => element.name == name, + orElse: () => null); return found; } diff --git a/lib/src/model/page_model.dart b/lib/src/model/page_model.dart index 01d5f6e..eca7fdb 100644 --- a/lib/src/model/page_model.dart +++ b/lib/src/model/page_model.dart @@ -107,9 +107,9 @@ class PageModel extends ModelBase { /// Parses the map and stores the data in the instance. void parse() { - name = parseString('name', map, required: true); + name = parseString('page', map, required: true); checkSuperfluousAttributes( - map, 'name options pageType sections'.split(' ')); + map, 'options page pageType sections'.split(' ')); pageModelType = parseEnum( 'pageType', map, PageModelType.values, required: true); diff --git a/lib/src/model/standard/role_model.dart b/lib/src/model/standard/role_model.dart index bc6fec5..a1c6b1c 100644 --- a/lib/src/model/standard/role_model.dart +++ b/lib/src/model/standard/role_model.dart @@ -6,44 +6,37 @@ class RoleModel extends ModuleModel { "module": "role", "tables": [ { - 'name': 'role', + 'table': 'role', 'columns': [ { - 'name': 'role_id', + 'column': 'role_id', 'dataType': 'int', 'label': 'Id', 'options': 'primary', }, { - 'name': 'role_name', + 'column': 'role_name', 'dataType': 'string', 'label': 'Rolle', 'size': 32, 'options': 'unique;notnull', }, { - 'name': 'role_priority', + 'column': 'role_priority', 'dataType': 'int', 'label': 'Priorität', }, { - 'name': 'role_created', - 'dataType': 'dateTime', - 'label': 'Erzeugt', - 'options': 'hidden;null', - }, - { - 'name': 'role_changed', - 'dataType': 'dateTime', - 'label': 'Geändert', - 'options': 'hidden;null', + 'column': 'role_active', + 'dataType': 'bool', + 'label': 'Aktiv', }, ] }, ], 'pages': [ { - "name": "create", + "page": "create", "pageType": "create", "sections": [ { @@ -57,7 +50,7 @@ class RoleModel extends ModuleModel { ] }, { - "name": "change", + "page": "change", "pageType": "change", "sections": [ { @@ -71,7 +64,7 @@ class RoleModel extends ModuleModel { ] }, { - "name": "list", + "page": "list", "pageType": "list", "sections": [ { diff --git a/lib/src/model/standard/user_model.dart b/lib/src/model/standard/user_model.dart index 3411bff..84d18a9 100644 --- a/lib/src/model/standard/user_model.dart +++ b/lib/src/model/standard/user_model.dart @@ -6,44 +6,44 @@ class UserModel extends ModuleModel { "module": "user", "tables": [ { - 'name': 'users', + 'table': 'user', 'columns': [ { - 'name': 'user_id', + 'column': 'user_id', 'dataType': 'int', 'label': 'Id', 'options': 'primary', }, { - 'name': 'user_name', + 'column': 'user_name', 'dataType': 'string', 'label': 'User', 'size': 64, - 'options': 'unique', + 'options': 'unique;notnull', }, { - 'name': 'user_displayname', + 'column': 'user_displayname', 'dataType': 'string', 'label': 'Anzeigename', 'size': 32, 'options': 'unique', }, { - 'name': 'user_email', + 'column': 'user_email', 'dataType': 'string', 'label': 'EMail', 'size': 128, 'options': 'unique', }, { - 'name': 'user_password', + 'column': 'user_password', 'dataType': 'string', 'label': 'User', 'size': 128, 'options': 'password', }, { - 'name': 'user_role', + 'column': 'user_role', 'dataType': 'reference', 'label': 'Role', 'foreignKey': 'role.role_id', @@ -54,45 +54,55 @@ class UserModel extends ModuleModel { ], 'pages': [ { - "name": "create", + "page": "create", "pageType": "create", "sections": [ { "sectionType": "simpleForm", "children": [ { - "widgetType": "text", - "text": "*_Erfassung eines neuen Benutzers:_*", - "options": "rich", - }, - { - "widgetType": "emptyLine", - }, + "widgetType": "allDbFields", + } + ] + } + ] + }, + { + "page": "change", + "pageType": "change", + "sections": [ + { + "sectionType": "simpleForm", + "children": [ { - "widgetType": "textField", - "name": "user", - "label": "Benutzer", - "options": "required;unique", - }, + "widgetType": "allDbFields", + } + ] + } + ] + }, + { + "page": "list", + "pageType": "list", + "sections": [ + { + "sectionType": "filterPanel", + "children": [ { "widgetType": "textField", - "name": "displayname", - "label": "Anzeigename", - "fieldType": "text", - "options": "required", + "filterType": "pattern", + "name": "user_name", }, { "widgetType": "combobox", - "name": "role", - "label": "Rolle", - "dataType": "reference", - "options": "required;undef", - }, + "filterType": "equals", + "name": "user_role", + } ] } ] }, - ], + ] }; UserModel(BaseLogger logger) : super(model, logger); diff --git a/lib/src/model/table_model.dart b/lib/src/model/table_model.dart index f1d56f8..3deafec 100644 --- a/lib/src/model/table_model.dart +++ b/lib/src/model/table_model.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart'; import 'column_model.dart'; import 'model_base.dart'; +import 'model_types.dart'; import 'module_model.dart'; /// Represents a database table describing the data model of the model. @@ -59,8 +60,8 @@ class TableModel extends ModelBase { /// Parses the map and stores the data in the instance. void parse() { - name = parseString('name', map, required: true); - checkSuperfluousAttributes(map, 'name options columns'.split(' ')); + name = parseString('table', map, required: true); + checkSuperfluousAttributes(map, 'columns options table'.split(' ')); options = parseOptions('options', map); if (!map.containsKey('columns')) { logger.error('missing columns in table ${fullName()}'); @@ -74,11 +75,35 @@ class TableModel extends ModelBase { } } checkOptionsByRegExpr(options, regExprOptions); + _addIfMissing('${name}_createdat', 'Erzeugt', DataType.dateTime, + ['hidden', 'null', 'doStore']); + _addIfMissing('${name}_createdby', 'Erzeugt von', DataType.string, + ['hidden', 'doStore'], 16); + _addIfMissing('${name}_changedat', 'Geändert', DataType.dateTime, + ['hidden', 'null', 'doStore']); + _addIfMissing('${name}_changedby', 'Geändert von', DataType.string, + ['hidden', 'doStore'], 16); } @override widgetName() => name; + /// Adds a column [name] if it does not exist with [label] and [dataType]. + void _addIfMissing( + String name, String label, DataType dataType, List options, + [int size]) { + if (getColumn(name, required: false) == null) { + addColumn(ColumnModel.raw( + name: name, + table: this, + label: label, + dataType: dataType, + options: options ?? [], + size: size, + logger: logger)); + } + } + /// Returns a list of tables constructed by the Json like [list]. static void parseList( ModuleModel module, List list, BaseLogger logger) { diff --git a/lib/src/model/text_field_model.dart b/lib/src/model/text_field_model.dart index 2360c79..f3df8ea 100644 --- a/lib/src/model/text_field_model.dart +++ b/lib/src/model/text_field_model.dart @@ -20,7 +20,6 @@ class TextFieldModel extends FieldModel { FormFieldSetter onSaved; final Map map; - FilterType filterType; TextFieldModel( SectionModel section, PageModel page, this.map, BaseLogger logger) @@ -42,7 +41,6 @@ class TextFieldModel extends FieldModel { .split(' ')); maxSize = parseInt('maxSize', map, required: false); rows = parseInt('rows', map, required: false); - filterType = parseEnum('filterType', map, FilterType.values); switch (dataType) { case DataType.int: case DataType.reference: diff --git a/lib/src/model/text_model.dart b/lib/src/model/text_model.dart index 454b1f9..f16195d 100644 --- a/lib/src/model/text_model.dart +++ b/lib/src/model/text_model.dart @@ -6,7 +6,7 @@ import 'widget_model.dart'; /// Describes a text widget without user interaction. class TextModel extends WidgetModel { - static final regExprOptions = RegExp(r'^(richtext)$'); + static final regExprOptions = RegExp(r'^(rich)$'); List options; String text; bool isRichText; diff --git a/lib/src/model/widget_model.dart b/lib/src/model/widget_model.dart index 269964b..e7f75cc 100644 --- a/lib/src/model/widget_model.dart +++ b/lib/src/model/widget_model.dart @@ -16,7 +16,6 @@ abstract class WidgetModel extends ModelBase { : super(logger) { this.id = ++lastId; } - /// Dumps the internal structure into a [stringBuffer] StringBuffer dump(StringBuffer stringBuffer); diff --git a/lib/src/tool/db_tool.dart b/lib/src/tool/db_tool.dart deleted file mode 100644 index 984ac4b..0000000 --- a/lib/src/tool/db_tool.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_bones/flutter_bones.dart'; -import 'package:test/test.dart'; - -import '../model/standard/role_model.dart'; -import '../model/standard/user_model.dart'; - -void dummy() { - group('xxx', () { - test('y', () { - Widget text = Text(''); - expect(text, isNotNull); - }); - }); -} - -void main(List argv) async { - final logger = MemoryLogger(LEVEL_FINE); - if (argv.length > 0xffff) { - dummy(); - } - final dbHelper = DbHelper(logger); - if (argv.length == 0) { - logger.error("missing arguments"); - } else { - final mode = argv[0]; - argv.removeAt(0); - switch (mode) { - case 'create-sql': - dbHelper.exportSql(argv); - break; - default: - logger.error('unknown mode: ${argv[0]}'); - break; - } - } - //await tool.main(); -} - -class DbHelper { - final BaseLogger logger; - - DbHelper(this.logger); - - void exportSql(List argv) { - if (argv.length == 0) { - logger.error('missing table'); - } else { - final table = argv[0]; - argv.removeAt(0); - ModuleModel module; - switch (table) { - case 'user': - module = UserModel(logger); - break; - case 'role': - module = RoleModel(logger); - break; - default: - logger.error('unknown table'); - break; - } - final filename = argv.length == 0 ? '$table.sql' : argv[0]; - FileSync.toFile(filename, module.exportSql()); - print('exported: $filename'); - } - } - - void usage(String error) { - print('''usage: dbhelper [] -: - create-sql [] - : 'role', 'user' ... -Examples: -dbhelper create-sql role role.sql -'''); - } -} diff --git a/lib/src/widget/filters.dart b/lib/src/widget/filters.dart index fbdb7dd..99e290f 100644 --- a/lib/src/widget/filters.dart +++ b/lib/src/widget/filters.dart @@ -77,7 +77,7 @@ class Filters void add(FilterItem item) => filters.add(item); FilterItem byName(String name) => - filters.firstWhere((element) => element.name == name); + filters.firstWhere((element) => element.name == name, orElse: () => null); @override getOnChanged(String customString, TextCallbackController controller) { diff --git a/pubspec.yaml b/pubspec.yaml index e1d0a53..ffc1db6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - dart_bones: "^0.2.2" + dart_bones: "^0.4.3" # The following adds the Cupertino Icons font to your application. diff --git a/test/helpers/string_helper_test.dart b/test/helpers/string_helper_test.dart index 8de5891..7330334 100644 --- a/test/helpers/string_helper_test.dart +++ b/test/helpers/string_helper_test.dart @@ -1,7 +1,13 @@ import 'package:flutter_bones/flutter_bones.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart'; import 'package:test/test.dart'; void main() { + setUpAll(() { + Intl.defaultLocale = "de_DE"; + initializeDateFormatting(); + }); group('fromString', () { test('fromString-string', () { expect(StringHelper.fromString('abc', DataType.string), equals('abc')); @@ -40,4 +46,57 @@ void main() { startsWith('[]; + expect(StringHelper.splitArgv(args, options), equals(['a', 'b'])); + expect(options, equals(['-a'])); + options.clear(); + args = ['-a', '--b=c']; + expect(StringHelper.splitArgv(args, options), equals([])); + expect(options, equals(args)); + options.clear(); + args = ['a']; + expect(StringHelper.splitArgv(args, options), equals(args)); + expect(options, equals([])); + options.clear(); + args = []; + expect(StringHelper.splitArgv(args, options), equals(args)); + expect(options, equals([])); + }); + }); } diff --git a/test/model/db_model_test.dart b/test/model/db_model_test.dart index 8001a31..76ad5e6 100644 --- a/test/model/db_model_test.dart +++ b/test/model/db_model_test.dart @@ -16,18 +16,26 @@ void main() { final page = module.pageByName('create'); expect(page?.fullName(), equals('demo1.create')); expect(module.fullName(), equals('demo1')); - final table = module.tableByName('users'); - expect(table.fullName(), equals('demo1.users')); - expect(table.widgetName(), equals('users')); + final table = module.tableByName('user'); + expect(table.fullName(), equals('demo1.user')); + expect(table.widgetName(), equals('user')); final dump = module.dump(StringBuffer()).toString(); expect(dump, '''= module demo1: options: -== table users: options: - column user_id: DataType.int "Id" options: primary +== table user: options: + column user_id: DataType.int "Id" options: primary notnull unique column user_name: DataType.string "User" options: unique column user_role: DataType.reference "Role" options: -== table roles: options: - column role_id: DataType.int "Id" options: primary + column user_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore + column user_createdby: DataType.string "Erzeugt von" options: hidden doStore + column user_changedat: DataType.dateTime "Geändert" options: hidden null doStore + column user_changedby: DataType.string "Geändert von" options: hidden doStore +== table role: options: + column role_id: DataType.int "Id" options: primary notnull unique column role_name: DataType.string "Role" options: unique + column role_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore + column role_createdby: DataType.string "Erzeugt von" options: hidden doStore + column role_changedat: DataType.dateTime "Geändert" options: hidden null doStore + column role_changedby: DataType.string "Geändert von" options: hidden doStore == page create: PageModelType.create options: = section simpleForm1: SectionModelType.simpleForm options: [ textField user: options: required unique @@ -38,7 +46,7 @@ void main() { final userField = table.getColumn('user_id'); expect(userField, isNotNull); expect(userField.widgetName(), 'user_id'); - expect(userField.fullName(), equals('users.user_id')); + expect(userField.fullName(), equals('user.user_id')); final userRef = page.getField('user'); expect(userRef, isNotNull); expect(userRef.fullName(), equals('create.user')); @@ -51,23 +59,23 @@ final userModel = { 'module': 'demo1', 'tables': [ { - 'name': 'users', + 'table': 'user', 'columns': [ { - 'name': 'user_id', + 'column': 'user_id', 'dataType': 'int', 'label': 'Id', 'options': 'primary', }, { - 'name': 'user_name', + 'column': 'user_name', 'dataType': 'string', 'label': 'User', 'size': 32, 'options': 'unique', }, { - 'name': 'user_role', + 'column': 'user_role', 'dataType': 'reference', 'label': 'Role', 'foreignKey': 'role.role_id', @@ -76,16 +84,16 @@ final userModel = { ] }, { - 'name': 'roles', + 'table': 'role', 'columns': [ { - 'name': 'role_id', + 'column': 'role_id', 'dataType': 'int', 'label': 'Id', 'options': 'primary', }, { - 'name': 'role_name', + 'column': 'role_name', 'dataType': 'string', 'label': 'Role', 'size': 32, @@ -96,7 +104,7 @@ final userModel = { ], 'pages': [ { - 'name': 'create', + 'page': 'create', 'pageType': 'create', 'sections': [ { @@ -112,7 +120,7 @@ final userModel = { { 'widgetType': 'dbReference', 'name': 'role', - 'column': 'roles.role_id', + 'column': 'role.role_id', }, { 'widgetType': 'button', diff --git a/test/model/model_test.dart b/test/model/model_test.dart index 60e97b8..9e85c68 100644 --- a/test/model/model_test.dart +++ b/test/model/model_test.dart @@ -166,7 +166,7 @@ void main() { var module = Demo1(map, logger); module.parse(); var errors = logger.errors; - expect(errors.length, equals(3)); + expect(errors.length, equals(2)); final dump = module.dump(StringBuffer()).toString(); expect(dump, equals('''= module demo1: options: == page create: PageModelType.create options: @@ -190,13 +190,19 @@ void main() { expect(nonFieldWidgets.length, equals(2)); }); }); + group('standard-models', () { + test('user', () { + final model = UserModel(logger); + model.parse(); + }); + }); } final userModel = { 'module': 'demo1', 'pages': [ { - 'name': 'create', + 'page': 'create', 'pageType': 'create', 'sections': [ { diff --git a/test/tool/tool_test.dart b/test/tool/tool_test.dart index 506f74a..023c466 100644 --- a/test/tool/tool_test.dart +++ b/test/tool/tool_test.dart @@ -5,21 +5,61 @@ import 'package:test/test.dart'; void main() { final logger = MemoryLogger(LEVEL_FINE); group('sql', () { - test('export-sql', () { + test('exportSqlCreateTablel', () { logger.clear(); final module = RoleModel(logger); module.parse(); - final content = module.exportSql(); + final content = module.exportSqlCreateTable(); final errors = logger.errors; expect(errors.length, equals(0)); - expect(content, equals('''drop table if exists role; -create table role ( - role_id int(10) primary; - role_name varchar(255) unique notnull; - role_priority int(10); - role_created timestamp null; - role_changed timestamp null; + expect(content, equals('''DROP TABLE IF EXISTS role; +CREATE TABLE role ( + role_id INT(10) notnull unique, + role_name VARCHAR(32) unique notnull, + role_priority INT(10), + role_active CHAR(1), + role_createdat TIMESTAMP null, + role_createdby VARCHAR(16), + role_changedat TIMESTAMP null, + role_changedby VARCHAR(16), + PRIMARY KEY(role_id) ); +''')); + }); + test('exportSqlBackend', () { + logger.clear(); + final module = RoleModel(logger); + module.parse(); + final content = module.exportSqlBackend(testDate: true); + final errors = logger.errors; + expect(errors.length, equals(0)); + expect(content, equals('''--- +# configuration of the bones backend for role: +created: 2020.01.01 00:00:00 +author: flutter_bones.module_model.exportSqlBackend() +version: 1.0.0 +modules: + - module: role + list: + - name: insert + type: insert + sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby,role_changedat,role_changedby) + VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby,:role_changedat,:role_changedby);" + - name: update + type: update + sql: "UPDATE role SET + role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_createdat=:role_createdat,role_createdby=:role_createdby,role_changedat=NOW(),role_changedby=:role_changedby + WHERE role_id=:role_id"; + - name: delete + type: delete + sql: "DELETE from role WHERE role_id=:role_id;" + - name: record + type: record + sql: "SELECT * from role WHERE role_id=:role_id;" + - name: list + type: list + sql: "SELECT * from role + WHERE role_name like :role_name;" ''')); }); });