]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
Generator RestServer GlobalData edit/delete-routing
authorHamatoma <author.hamatoma.de>
Wed, 6 Oct 2021 21:20:26 +0000 (23:20 +0200)
committerHamatoma <author.hamatoma.de>
Wed, 6 Oct 2021 21:21:16 +0000 (23:21 +0200)
* Generator:
** Refactoring: PageGenerator und SqlGenerator
** Sql: Generation of foreign keys

* RestServer:
** Wertet Offset/Size (Paginierung) bei List aus
** Automatische generierung des SELECT count(*) ...-Statements

* GlobalData:
** Refactoring: the singleton instance is fetched later/deeper in class hierarchy

* edit/delete-routing: the routing contains at least the primary key

* persistence: Refactoring: Result of query() is now DbData

39 files changed:
lib/exhibition_app.dart
lib/meta/module_meta_data.dart
lib/meta/modules.dart
lib/meta/users_meta.dart
lib/page/info_page.dart
lib/page/log_page.dart
lib/page/page_collection.dart
lib/page/page_collection_custom.dart
lib/page/roles/create_role_custom.dart
lib/page/roles/create_role_page.dart
lib/page/roles/edit_role_custom.dart
lib/page/roles/edit_role_page.dart
lib/page/roles/list_role_custom.dart
lib/page/roles/list_role_page.dart
lib/page/users/create_user_custom.dart
lib/page/users/create_user_page.dart
lib/page/users/delete_user_custom.dart
lib/page/users/delete_user_page.dart
lib/page/users/edit_user_custom.dart
lib/page/users/edit_user_page.dart
lib/page/users/list_user_custom.dart
lib/page/users/list_user_page.dart
lib/persistence/file_persistence.dart
lib/persistence/persistence.dart
lib/persistence/rest_persistence.dart
lib/setting/drawer_exhibition.dart
lib/widget/attended_page.dart
metatool/bin/page_generator.dart
metatool/bin/sql_generator.dart
metatool/lib/base [new symlink]
metatool/lib/meta [new symlink]
metatool/rest_server/data/sql/roles.sql.yaml [new file with mode: 0644]
metatool/rest_server/data/sql/users.sql.yaml [new file with mode: 0644]
rest_client/lib/base [new symlink]
rest_client/test/rest_client_test.dart
rest_server/data/sql/roles.sql.yaml
rest_server/data/sql/users.sql.yaml
rest_server/lib/rest_server.dart
rest_server/test/sql_services_test.dart

index bc485e871301c18d8f4ed438a3c374d4fcd29d4c..148a4f24203e8c608490fe5f8db3211d96efa7fa 100644 (file)
@@ -12,13 +12,13 @@ Route<dynamic>? _getRoute(RouteSettings settings) {
   StatefulWidget? page;
   switch (settings.name) {
     case '/start':
-      page = StartPage(globalLogger);
+      page = StartPage(GlobalData().logger);
       break;
     case '/info':
-      page = InfoPage(GlobalData());
+      page = InfoPage();
       break;
     case '/log':
-      page = LogPage(GlobalData());
+      page = LogPage();
       break;
     default:
       page = PageCollection().newPageByRoute(settings.name ?? '');
index 6c4f4f867936104f8e4f7b3597c91b991738e830..b7c507c489dd79ba1af3ed1019d02c9b16d9d2d8 100644 (file)
@@ -279,12 +279,12 @@ class PropertyMetaData {
   String columnName;
 
   /// The foreign key if dataType is DataType.reference, e.g. 'users.user_id'
-  String? reference;
+  String? foreignKey;
   PropertyMetaData(this.name, this.label, this.dataType, this.options,
       {this.displayType = DisplayType.text,
       this.columnName = '',
       this.size = 0,
-      this.reference,
+      this.foreignKey,
       int weight = 6}) {
     this.weight = weight > 12 ? 12 : weight;
   }
index 2ee28919518c3671b3244888155b1e79874b203c..57e2fe3cf5b07ab4a0c6658590fd21400ecd6642 100644 (file)
@@ -2,7 +2,6 @@
 import 'module_meta_data.dart';
 import 'roles_meta.dart';
 import 'users_meta.dart';
-
 /// Returns the meta data of the module given by [name].
 /// Returns null if not found.
 ModuleMetaData? moduleByName(String name) {
@@ -19,9 +18,8 @@ ModuleMetaData? moduleByName(String name) {
   }
   return rc;
 }
-
 /// Returns the module names as string list.
-List<String> moduleNames() {
+List<String> moduleNames(){
   return [
     'Roles',
     'Users',
index af9ba008fd2eb2e198136301f88bf835f1ab750a..53b9a2bbe620f1d74107cbe3be1515eef6611b0e 100644 (file)
@@ -25,7 +25,7 @@ class UserMeta extends ModuleMetaData {
               size: 255),
           PropertyMetaData(
               'role', i18n.tr('Role'), DataType.reference, ':notnull:',
-              displayType: DisplayType.combobox, reference: 'roles.role_id'),
+              displayType: DisplayType.combobox, foreignKey: 'roles.role_id;role_name;role'),
           PropertyMetaData(
               'created', i18n.tr('Created'), DataType.datetime, ':hidden:'),
           PropertyMetaData(
index ea8b7bc6e06e8a3957614281682ff980152f8d0b..9ae2ed8ec26c042428da223d44c354fedb22d739 100644 (file)
@@ -6,19 +6,14 @@ import 'package:url_launcher/url_launcher.dart';
 import '../setting/global_data.dart';
 
 class InfoPage extends StatefulWidget {
-  final GlobalData globalData;
-
-  const InfoPage(this.globalData, {Key? key}) : super(key: key);
-
   @override
-  InfoPageState createState() {
-    return InfoPageState();
+  _InfoPageState createState() {
+    return _InfoPageState(GlobalData());
   }
 }
 
-class InfoPageState extends State<InfoPage> {
-  final GlobalData globalData = GlobalData();
-
+class _InfoPageState extends State<InfoPage> {
+  final GlobalData globalData;
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
 
   final markdownData = '''# Pollector
@@ -49,7 +44,7 @@ Zur Entwicklung wurden einige **Opensource-Pakete** verwendet:
 ''';
   int clickCounter = 0;
 
-  InfoPageState();
+  _InfoPageState(this.globalData);
 
   @override
   Widget build(BuildContext context) {
index 3e7d363b00bfc42265816ccb4a46ebfef683ac0a..522b9c723ee6f6f1174b41544e01f957cb510ff9 100644 (file)
@@ -5,24 +5,20 @@ import 'package:flutter/material.dart';
 import '../setting/global_data.dart';
 
 class LogPage extends StatefulWidget {
-  final GlobalData globalData;
-
-  const LogPage(this.globalData, {Key? key}) : super(key: key);
-
   @override
   LogPageState createState() {
-    return LogPageState();
+    return LogPageState(GlobalData());
   }
 }
 
 class LogPageState extends State<LogPage> {
-  final GlobalData globalData = GlobalData();
+  final GlobalData globalData;
 
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
 
   String logName = '';
 
-  LogPageState();
+  LogPageState(this.globalData);
 
   @override
   Widget build(BuildContext context) {
index 675ce07a42ba16d21520ee7353b2f9e597396b0b..eca445f8de303b6ff23a9ff1ca4d89eb01f3a366 100644 (file)
@@ -1,6 +1,5 @@
 // DO NOT CHANGE. This file is created by the meta_tool!
 import 'package:flutter/material.dart';
-import '../setting/global_data.dart';
 import 'page_collection_custom.dart';
 
 import 'roles/create_role_page.dart';
@@ -11,6 +10,7 @@ import 'users/edit_user_page.dart';
 import 'users/delete_user_page.dart';
 import 'users/list_user_page.dart';
 
+
 /// Manages all meta data driven pages of the program.
 class PageCollection {
   static PageCollection? _instance;
@@ -24,31 +24,32 @@ class PageCollection {
   /// Creates a page defined by a [route].
   StatefulWidget? newPageByRoute(String route) {
     StatefulWidget? rc;
-    final globalData = GlobalData();
-    switch (route) {
+    final parts = route.split(';');
+    final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]);
+    switch (parts[0]) {
       case '/roles/create':
-        rc = CreateRolePage(globalData);
+        rc = CreateRolePage();
         break;
       case '/roles/edit':
-        rc = EditRolePage(globalData);
+        rc = EditRolePage(arg1);
         break;
       case '/roles/list':
-        rc = ListRolePage(globalData);
+        rc = ListRolePage();
         break;
       case '/users/create':
-        rc = CreateUserPage(globalData);
+        rc = CreateUserPage();
         break;
       case '/users/edit':
-        rc = EditUserPage(globalData);
+        rc = EditUserPage(arg1);
         break;
       case '/users/delete':
-        rc = DeleteUserPage(globalData);
+        rc = DeleteUserPage(arg1);
         break;
       case '/users/list':
-        rc = ListUserPage(globalData);
+        rc = ListUserPage();
         break;
       default:
-        rc = customPageByRoute(route, globalData);
+        rc = customPageByRoute(route);
         break;
     }
     if (rc != null) {
index c61ba97e4bf40fde18d08f808cfd6ce841e9cdb3..d9c8dc958f6cfe08bf43b6294f1c3b942a1eadf0 100644 (file)
@@ -1,21 +1,22 @@
 // This file is created by the meta_tool. But it can be customized.
 // It will never overridden by the meta_tool.
 import 'package:flutter/material.dart';
-import '../setting/global_data.dart';
 import 'info_page.dart';
 import 'log_page.dart';
 
 /// Creates a page defined by a [route].
 ///
 /// Note: only pages without meta data definitions should be handled here.
-StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
+StatefulWidget? customPageByRoute(String route) {
   StatefulWidget? rc;
-  switch (route) {
+  final parts = route.split(';');
+  //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]);
+  switch (parts[0]) {
     case '/info':
-      rc = InfoPage(globalData);
+      rc = InfoPage();
       break;
     case '/log':
-      rc = LogPage(globalData);
+      rc = LogPage();
       break;
     default:
       break;
index 7d338e824f01b7cb32002e38d121080b122bf05b..90fe008bf9600f84f4fb9766de5d0edb429877c5 100644 (file)
@@ -14,6 +14,7 @@ class CreateRoleCustom extends State<CreateRolePage> {
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  CreateRoleCustom();
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +27,6 @@ class CreateRoleCustom extends State<CreateRolePage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index 6018fb8555bb4dd58b9194908bcda50be47a6c12..4ec6c88d247f4f8f2e59f42a4159ecb4d1dbb6e0 100644 (file)
@@ -7,20 +7,20 @@ import '../../widget/attended_page.dart';
 import 'create_role_custom.dart';
 
 class CreateRolePage extends StatefulWidget {
-  final GlobalData globalData;
   final PageStates pageStates = PageStates();
-  CreateRolePage(this.globalData) : super();
+  CreateRolePage() : super();
   @override
   _CreateRolePageState createState() {
     final rc = _CreateRolePageState();
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _CreateRolePageState extends CreateRoleCustom {
+  _CreateRolePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index b66d65bb18811e8690f99413f5fd4ee43f44922f..99b664ba752efbbb26df1db22a20d6231382ea71 100644 (file)
@@ -11,9 +11,11 @@ import 'edit_role_page.dart';
 final i18n = I18N();
 
 class EditRoleCustom extends State<EditRolePage> {
+  final int primaryKey;
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  EditRoleCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +28,6 @@ class EditRoleCustom extends State<EditRolePage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index 309976c9c9bcb8e4008c1eaa626ca952335e6a5f..ef15ac45d8661adb74e1558911f0d05741b1b6b9 100644 (file)
@@ -7,20 +7,21 @@ import '../../widget/attended_page.dart';
 import 'edit_role_custom.dart';
 
 class EditRolePage extends StatefulWidget {
-  final GlobalData globalData;
+  final int primaryKey;
   final PageStates pageStates = PageStates();
-  EditRolePage(this.globalData) : super();
+  EditRolePage(this.primaryKey) : super();
   @override
   _EditRolePageState createState() {
-    final rc = _EditRolePageState();
+    final rc = _EditRolePageState(this.primaryKey);
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _EditRolePageState extends EditRoleCustom {
+  _EditRolePageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index f367ee21907da7617c779b88fead734aee5f5ef9..d85fc58eabdc7384cd7935cf0822c747c42ab244 100644 (file)
@@ -14,6 +14,7 @@ class ListRoleCustom extends State<ListRolePage> {
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  ListRoleCustom();
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +27,6 @@ class ListRoleCustom extends State<ListRolePage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index b2f01d40f36a1c608d22965fdb5d4cee79ace221..b42f052403af7c5e5350e744c837b61cbd7126b8 100644 (file)
@@ -7,20 +7,20 @@ import '../../widget/attended_page.dart';
 import 'list_role_custom.dart';
 
 class ListRolePage extends StatefulWidget {
-  final GlobalData globalData;
   final PageStates pageStates = PageStates();
-  ListRolePage(this.globalData) : super();
+  ListRolePage() : super();
   @override
   _ListRolePageState createState() {
     final rc = _ListRolePageState();
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _ListRolePageState extends ListRoleCustom {
+  _ListRolePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 309f2e0a2da1bea49906c0f44cd8c17c93d98173..f6f70afcb8b5686d3b785f474323f08d70a0f37a 100644 (file)
@@ -14,6 +14,7 @@ class CreateUserCustom extends State<CreateUserPage> {
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  CreateUserCustom();
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +27,6 @@ class CreateUserCustom extends State<CreateUserPage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index dec4186d0e733e33c410d03b51324bdf3867c679..14e69e258185a24630f5a76f39e934f03133ccc6 100644 (file)
@@ -7,20 +7,20 @@ import '../../widget/attended_page.dart';
 import 'create_user_custom.dart';
 
 class CreateUserPage extends StatefulWidget {
-  final GlobalData globalData;
   final PageStates pageStates = PageStates();
-  CreateUserPage(this.globalData) : super();
+  CreateUserPage() : super();
   @override
   _CreateUserPageState createState() {
     final rc = _CreateUserPageState();
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _CreateUserPageState extends CreateUserCustom {
+  _CreateUserPageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 6a3dfd1933effb5df46b56852e1487dd12fae419..d36681a1a0ce16bae78175d45033cbbb75917c29 100644 (file)
@@ -11,9 +11,11 @@ import 'delete_user_page.dart';
 final i18n = I18N();
 
 class DeleteUserCustom extends State<DeleteUserPage> {
+  final int primaryKey;
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  DeleteUserCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +28,6 @@ class DeleteUserCustom extends State<DeleteUserPage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index e5ac100365f54d1b9672f6dbe1703e2e876d5adf..85cbfc907ceebe2c637ac1bc32c7ee0433342d34 100644 (file)
@@ -7,20 +7,21 @@ import '../../widget/attended_page.dart';
 import 'delete_user_custom.dart';
 
 class DeleteUserPage extends StatefulWidget {
-  final GlobalData globalData;
+  final int primaryKey;
   final PageStates pageStates = PageStates();
-  DeleteUserPage(this.globalData) : super();
+  DeleteUserPage(this.primaryKey) : super();
   @override
   _DeleteUserPageState createState() {
-    final rc = _DeleteUserPageState();
+    final rc = _DeleteUserPageState(this.primaryKey);
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _DeleteUserPageState extends DeleteUserCustom {
+  _DeleteUserPageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 3c2074520c6524a6fad7562d374f177d9ac2ad2c..3e5f5a386055b0a462b2c6a0baf26df4ce6421c0 100644 (file)
@@ -11,9 +11,11 @@ import 'edit_user_page.dart';
 final i18n = I18N();
 
 class EditUserCustom extends State<EditUserPage> {
+  final int primaryKey;
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  EditUserCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -26,7 +28,6 @@ class EditUserCustom extends State<EditUserPage> {
                 padding: padding)));
     return rc;
   }
-
   @override
   void initState() {
     super.initState();
index 4c6bcf3af0d053c94220d2013c2dfd3e249ee416..11796632306f7b6a0d8a5c1b26bd2b9bd80e6d06 100644 (file)
@@ -7,20 +7,21 @@ import '../../widget/attended_page.dart';
 import 'edit_user_custom.dart';
 
 class EditUserPage extends StatefulWidget {
-  final GlobalData globalData;
+  final int primaryKey;
   final PageStates pageStates = PageStates();
-  EditUserPage(this.globalData) : super();
+  EditUserPage(this.primaryKey) : super();
   @override
   _EditUserPageState createState() {
-    final rc = _EditUserPageState();
+    final rc = _EditUserPageState(this.primaryKey);
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _EditUserPageState extends EditUserCustom {
+  _EditUserPageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index e2b1db512f0d48c1e252fe81ed51ea0fc5fd3e5f..b3e40b860ffcb298203433bf71dce44d86a5a3a1 100644 (file)
@@ -14,16 +14,45 @@ class ListUserCustom extends State<ListUserPage> {
   final globalData = GlobalData();
   AttendedPage? attendedPage;
 
+  ListUserCustom();
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
+    final filters = WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+        screenWidth: attendedPage!.pageStates.screenWidth, padding: padding);
+    final rows = attendedPage!.getRows(
+        'user_id;user_displayname;user_email;role',
+        '/users/list',
+        {},
+        () => setState(() => 1),
+        '/users/edit',
+        context);
+    final table = DataTable(
+      columns: <DataColumn>[
+        DataColumn(
+          label: Text(i18n.tr('Id')),
+        ),
+        DataColumn(
+          label: Text(i18n.tr('Name')),
+        ),
+        DataColumn(
+          label: Text(i18n.tr('EMail')),
+        ),
+        DataColumn(
+          label: Text(i18n.tr('Role')),
+        ),
+      ],
+      rows: rows,
+    );
+    final frameWidget = Column(children: [
+      filters,
+      SizedBox(height: padding),
+      SizedBox(width: double.infinity, child: table),
+    ]);
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
         drawer: globalData.drawerBuilder(context),
-        body: SafeArea(
-            child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
-                screenWidth: attendedPage!.pageStates.screenWidth,
-                padding: padding)));
+        body: SafeArea(child: frameWidget));
     return rc;
   }
 
index b05bbf35dea25b0c174b2bdbee88ab1494cf19b8..1938d3d9a841fa556eb29744f91b85a73d9fde16 100644 (file)
@@ -7,20 +7,20 @@ import '../../widget/attended_page.dart';
 import 'list_user_custom.dart';
 
 class ListUserPage extends StatefulWidget {
-  final GlobalData globalData;
   final PageStates pageStates = PageStates();
-  ListUserPage(this.globalData) : super();
+  ListUserPage() : super();
   @override
   _ListUserPageState createState() {
     final rc = _ListUserPageState();
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _ListUserPageState extends ListUserCustom {
+  _ListUserPageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index c1bee7de066545363385a160a83701ea8afe349d..9637e75b89c022f183f4e5ca64d2c2f38a80d6f1 100644 (file)
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
 
 import 'persistence.dart';
 import 'data_record.dart';
+import '../base/defines.dart';
 
 /// Implements a persistence layer storing data in Json files.
 class FilePersistence extends Persistence {
@@ -30,7 +31,7 @@ class FilePersistence extends Persistence {
             logger: logger);
 
   @override
-  Future<dynamic> query({required String what, DataMap? data}) async {
+  Future<DbData> query({required String what, DataMap? data}) async {
     var rc;
     final fn = path.join(dataDirectory, '$what.json');
     final file = File(fn);
@@ -38,9 +39,17 @@ class FilePersistence extends Persistence {
       logger.error('missing $fn');
     } else {
       final content = await file.readAsString();
-      rc = convert.jsonDecode(content);
+      final data = convert.jsonDecode(content);
+      if (data is Map){
+        rc = DbData.single(data as JsonMap);
+      } else if (data is List){
+        rc = DbData.list(data as JsonList, -1);
+      } else if (data is String){
+        rc = DbData.message(data);
+      } else {
+        rc = DbData.message('unknown data type: ${data.runtimeType}');
+      }
     }
-
     return rc;
   }
 
index 1ef20cad55648c7996ce7e85198e3cd983fcbed4..39949efc7a955b376e1fd7f7c83652437f96a2b5 100644 (file)
@@ -1,11 +1,23 @@
+import '../base/defines.dart';
+
 import 'data_record.dart';
 
+class DbData {
+  final JsonMap? singleRecord;
+  final JsonList? recordList;
+  final int? count;
+  final String? message;
+  DbData(this.singleRecord, this.recordList, this.count, this.message);
+  DbData.single(JsonMap record): this(record, null, null, null);
+  DbData.list(JsonList records, int count): this(null, records, count, null);
+  DbData.message(String message): this(null, null, null, message);
+}
 abstract class Persistence {
   /// Fetches data specified by [what] and some [data].
   ///
   /// Returns null or a record (as a Map instance)
   /// or a list of records (as a List<Map> instance).
-  Future<dynamic> query({required String what, DataMap? data});
+  Future<DbData> query({required String what, DataMap? data});
 
   /// Stores a map or a data container in the persistence layer.
   ///
index a82556252d644e8e0a67f8aa852408558b6a5c2d..a00a6b7b70fc0a76e47caf0ddeb2d1ac4f59e446 100644 (file)
@@ -1,5 +1,6 @@
 import 'dart:convert' as convert;
 import 'dart:io';
+import 'dart:math';
 
 import 'package:dart_bones/dart_bones.dart';
 import 'package:http/http.dart' as http;
@@ -93,16 +94,29 @@ class RestPersistence extends Persistence {
   }
 
   @override
-  Future<dynamic> query(
+  Future<DbData> query(
       {required String what, Map<String, dynamic>? data}) async {
-    String rc;
+    DbData rc;
     final params2 = data == null ? '{}' : convert.jsonEncode(data);
     final answer = await runRequest(what, body: params2, headers: jsonHeader);
-    if (answer.isNotEmpty &&
-        (answer.startsWith('{') || answer.startsWith('['))) {
-      rc = convert.jsonDecode(answer);
+    if (answer.isEmpty){
+      rc = DbData.message('query(): empty result');
+    } else if (answer.startsWith('{')){
+      rc = DbData.single(convert.jsonDecode(answer));
+    } else if (answer.startsWith('[')) {
+      rc = DbData.list(convert.jsonDecode(answer), -1);
+    } else if (answer.startsWith('#')) {
+      int ix = answer.indexOf('[');
+      if (ix > 2 && ix < 8){
+        final count = int.tryParse(answer.substring(1, ix));
+        rc = DbData.list(convert.jsonDecode(answer.substring(ix)), count ?? -1);
+      } else {
+        rc = DbData.message(
+            'query(): unexpected answer prefix: '
+            '${answer.substring(0, min<int>(answer.length, 8))}');
+      }
     } else {
-      rc = answer;
+        rc = DbData.message(answer.substring(0, max<int>(answer.length, 120)));
     }
     if (logger.logLevel >= LEVEL_FINE) {
       logger.log('answer: ${limitString(answer, sqlTraceLimit)}');
index 70b884753f191a2d86c42e197717bd3e84f0f2d5..49822ef0f279abbb82511ec9e989af2eaa784286 100644 (file)
@@ -1,8 +1,7 @@
 import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/page/page_collection.dart';
 import 'package:flutter/material.dart';
 
-import '../page/info_page.dart';
-import '../page/log_page.dart';
 import 'global_data.dart';
 
 class DrawerExhibition extends Drawer {
@@ -74,6 +73,9 @@ class MenuConverter {
   IconData iconByName(String name, BaseLogger logger) {
     IconData? rc;
     switch (name) {
+      case 'people_outlined':
+        rc = Icons.people_outline;
+        break;
       case 'addchart_outlined':
         rc = Icons.addchart_outlined;
         break;
@@ -98,24 +100,6 @@ class MenuConverter {
     }
     return rc ?? Icons.access_alarm;
   }
-
-  /// Returns the page given by [name].
-  StatefulWidget? pageByName(String name, GlobalData globalData) {
-    StatefulWidget? rc;
-    switch (name) {
-      case 'log':
-        rc = LogPage(globalData);
-        break;
-      case 'info':
-        rc = InfoPage(globalData);
-        break;
-      default:
-        globalData.logger
-            .error('MenuConverter.pageByName(): unknown page $name');
-        break;
-    }
-    return rc;
-  }
 }
 
 class MenuItem {
@@ -128,14 +112,17 @@ class MenuItem {
   static List<MenuItem> menuItems(MenuConverter converter) {
     final globalData = GlobalData();
     final logger = globalData.logger;
+    final collection = PageCollection();
     return <MenuItem>[
-      MenuItem('Protokoll', () => converter.pageByName('log', globalData),
+      MenuItem('Users', () => collection.newPageByRoute('/users/list'),
+          converter.iconByName('people_outlined', logger)),
+      MenuItem('Protokoll', () => collection.newPageByRoute('/log'),
           converter.iconByName('line_weight_outlined', logger)),
-      MenuItem('Information', () => converter.pageByName('info', globalData),
+      MenuItem('Information', () => collection.newPageByRoute('/info'),
           converter.iconByName('info_outline', logger)),
       MenuItem(
           'Konfiguration',
-          () => converter.pageByName('configuration', globalData),
+          () => collection.newPageByRoute('/configuration'),
           converter.iconByName('settings_applications_outlined', logger)),
     ];
   }
index 7dae3c030b2e422b4ae54ebb2228f0d3cb5f702d..de14e2a01530a95e60eb1461cdf7bd44a7f645f2 100644 (file)
@@ -1,3 +1,4 @@
+import 'package:exhibition/persistence/persistence.dart';
 import 'package:exhibition/widget/attended_widget.dart';
 import 'package:flutter/material.dart';
 
@@ -46,6 +47,40 @@ class AttendedPage {
     return rc;
   }
 
+  /// Returns a list of DataRow instances to show the content of some
+  /// database records in a [DataTable].
+  ///
+  /// [columnList]: a ';' delimited list of column names, e.g. 'user_id;user_name'
+  List<DataRow> getRows(String columnList, String what,
+      Map<String, dynamic> parameters, Function() onDone, String routeEdit, BuildContext context) {
+    List<DataRow>? rc;
+    if (!pageStates.openQuery) {
+      pageStates.openQuery = true;
+      globalData.restPersistence!
+          .query(what: what, data: parameters)
+          .then((value) {
+        pageStates.dbData = value;
+        pageStates.openQuery = false;
+        onDone();
+      });
+    } else if (pageStates.dbData != null) {
+      if (pageStates.dbData?.recordList != null) {
+        rc = <DataRow>[];
+        pageStates.dbData?.recordList!.map((record) {
+          final cells = <DataCell>[];
+          for (var column in columnList.split(';')){
+            final primaryKey = moduleMetaData.primaryOf();
+            cells.add(DataCell(Text(record[column].toString()),
+            onTap: () => globalData.navigate(context, routeEdit + ';${record[primaryKey]}')));
+          }
+          final rc3 = DataRow(cells: cells);
+          return rc3;
+        });
+      }
+    }
+    return rc ?? <DataRow>[];
+  }
+
   /// Validates the text [value] of the [textAttended].
   ///
   /// Returns null on success or the error message on error.
@@ -68,4 +103,6 @@ class PageStates {
   double screenWidth = 0;
   double screenHeight = 0;
   AttendedPage? attendedPage;
+  DbData? dbData;
+  bool openQuery = false;
 }
index 6c13ce6a3c0abbd25c7e616b631cbfedfc2b0d2f..34ba5f225fbd9354ca6021551790c9de3f03a2ce 100644 (file)
@@ -5,7 +5,6 @@ import 'package:dart_bones/dart_bones.dart';
 import '../lib/meta/module_meta_data.dart';
 import '../lib/meta/modules.dart';
 import 'generator.dart';
-import 'generator_base.dart';
 import 'sql_generator.dart';
 
 /// Handles the page generation. Is a base class of [Generator].
@@ -20,20 +19,20 @@ import '../../widget/attended_page.dart';
 import 'edit_user_custom.dart';
 
 class EditUserPage extends StatefulWidget {
-  final GlobalData globalData;
-  final PageStates pageStates = PageStates();
-  EditUserPage(this.globalData) : super();
+DEF_PRIMARY  final PageStates pageStates = PageStates();
+  EditUserPage(THIS_PRIMARY) : super();
   @override
   _EditUserPageState createState() {
-    final rc = _EditUserPageState();
+    final rc = _EditUserPageState(THIS_PRIMARY);
     rc.attendedPage =
-        AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+        AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _EditUserPageState extends EditUserCustom {
+  _EditUserPageState(PARAM_PRIMARY): super(VAL_PRIMARY);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
@@ -57,10 +56,10 @@ import 'edit_user_page.dart';
 final i18n = I18N();
 
 class EditUserCustom extends State<EditUserPage> {
-  final globalData = GlobalData();
+DEF_PRIMARY  final globalData = GlobalData();
   AttendedPage? attendedPage;
 
-
+  EditUserCustom(THIS_PRIMARY);
   @override
   Widget build(BuildContext context) {
     final padding = 16.0;
@@ -81,7 +80,6 @@ class EditUserCustom extends State<EditUserPage> {
 ''';
   static final templateCollection = '''// DO NOT CHANGE. This file is created by the meta_tool!
 import 'package:flutter/material.dart';
-import '../setting/global_data.dart';
 import 'page_collection_custom.dart';
 
 #IMPORTS#
@@ -99,10 +97,11 @@ class PageCollection {
   /// Creates a page defined by a [route].
   StatefulWidget? newPageByRoute(String route) {
     StatefulWidget? rc;
-    final globalData = GlobalData();
-    switch (route) {
+    final parts = route.split(';');
+    final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]);
+    switch (parts[0]) {
 #CASES#      default:
-        rc = customPageByRoute(route, globalData);
+        rc = customPageByRoute(route);
         break;
     }
     if (rc != null) {
@@ -120,21 +119,22 @@ class PageCollection {
   static final templateCollectionCustom = '''// This file is created by the meta_tool. But it can be customized.
 // It will never overridden by the meta_tool.
 import 'package:flutter/material.dart';
-import '../setting/global_data.dart';
 import 'info_page.dart';
 import 'log_page.dart';
 
 /// Creates a page defined by a [route].
 ///
 /// Note: only pages without meta data definitions should be handled here.
-StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
+StatefulWidget? customPageByRoute(String route) {
   StatefulWidget? rc;
-  switch (route) {
+  final parts = route.split(';');
+  //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]);
+  switch (parts[0]) {
     case '/info':
-      rc = InfoPage(globalData);
+      rc = InfoPage();
       break;
     case '/log':
-      rc = LogPage(globalData);
+      rc = LogPage();
       break;
     default:
       break;
@@ -143,7 +143,7 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
 }
 ''';
   static final templateCase = '''      case '/users/edit':
-        rc = EditUserPage(globalData);
+        rc = EditUserPage(ARG1);
         break;
 ''';
   PageGenerator(BaseLogger logger) : super(logger);
@@ -164,12 +164,30 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
   /// Returns a Dart class definition of the [page] that should not be modified.
   String createCustomized(PageMetaData page) {
     var rc = replaceVariables(templateCustom, page);
+    if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
+      rc = rc.replaceAll('DEF_PRIMARY', '  final int primaryKey;\n')
+          .replaceAll('THIS_PRIMARY', 'this.primaryKey');
+    } else {
+      rc = rc.replaceAll('DEF_PRIMARY', '')
+          .replaceAll('THIS_PRIMARY', '');
+    }
     return rc;
   }
 
   /// Returns a Dart class definition of the [page] that should not be modified.
   String createPage(PageMetaData page) {
     var rc = replaceVariables(templatePage, page);
+    if (page.pageType == PageType.edit || page.pageType == PageType.delete){
+      rc = rc.replaceAll('DEF_PRIMARY', '  final int primaryKey;\n')
+          .replaceAll('THIS_PRIMARY', 'this.primaryKey')
+          .replaceAll('PARAM_PRIMARY', 'int primaryKey')
+          .replaceAll('VAL_PRIMARY', 'primaryKey');
+    } else {
+      rc = rc.replaceAll('DEF_PRIMARY', '')
+          .replaceAll('THIS_PRIMARY', '')
+          .replaceAll('PARAM_PRIMARY', '')
+          .replaceAll('VAL_PRIMARY', '');
+    }
     return rc;
   }
 
@@ -184,7 +202,13 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
       for (var page in module!.pageList) {
         imports.writeln("import '${name.toLowerCase()}/${page.name}_"
         "${module.moduleNameSingular.toLowerCase()}_page.dart';");
-        cases.write(replaceVariables(templateCase, page));
+        var case1 = replaceVariables(templateCase, page);
+        if (page.pageType == PageType.edit || page.pageType == PageType.delete){
+          case1 = case1.replaceFirst('ARG1', 'arg1');
+        } else {
+          case1 = case1.replaceFirst('ARG1', '');
+        }
+        cases.write(case1);
       }
     }
     final content = templateCollection.replaceFirst('#IMPORTS#', imports.toString())
index 8efbb1d30a20d9f2d4eb63dfc1bc653ada5b5857..bce05472746743875afb880aac49f3d934fd48a1 100644 (file)
@@ -42,23 +42,29 @@ class SqlGenerator extends GeneratorBase {
     final buffer = StringBuffer();
     buffer.write('---\n');
     buffer.write('# DO NOT CHANGE. This file is created by the meta_tool\n');
-    buffer.write('''# SQL statements of the module "$moduleName":\n
+    var sqlText = '''# SQL statements of the module "$moduleName":\n
 module: $moduleName
 list:
   type: list
   parameters: []
-  sql: "select * from $tableName;"
+  sql: "SELECT
+    t0.*SELECTS
+    FROM $tableName t0
+JOINS
+    ;"
 byId:
   type: record
   parameters: [ "${list[0].name}" ]
-  sql: "select * from $tableName where ${list[0].columnName}=:${list[0].name};"
+  sql: "SELECT * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};"
 delete:
   type: delete
   parameters: [ "${list[0].name}" ]
-  sql: "delete * from $tableName where ${list[0].columnName}=:${list[0].name};"
+  sql: "DELETE * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};"
 update:
   type: update
-''');
+''';
+    sqlText = handleReferences(sqlText, module);
+    buffer.write(sqlText);
     var items = module.standardColumns('changedBy');
     var parameters = addToBuffer('  parameters: [', maxLength: 80);
     var assignments = addToBuffer('    ', maxLength: 80);
@@ -135,9 +141,40 @@ update:
         logger.error('+++ unknown module: $name');
       } else {
         logger.log('current directory: ${Directory.current.path}');
-        String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
+        String filename = '../rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
         writeFile(filename, generator.createSqlStatements(module));
       }
     }
   }
+  /// Handles the foreign keys in the [sqlText] of the given [module].
+  ///
+  /// Replaces the placeholders SELECTS and JOINS in [sqlText].
+  ///
+  /// Returns the modified SQL text.
+  String handleReferences(String sqlText, ModuleMetaData module) {
+    var joins = '';
+    var selects = '';
+    var referenceNo = 0;
+    for (var property in module.propertyList){
+      if (property.foreignKey != null){
+        // foreignKey: 'roles.role_id;role_name;role'
+        final parts = property.foreignKey!.split(';');
+        if (parts.length != 3){
+          logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
+        } else {
+          final keyParts = parts[0].split('.');
+          if (keyParts.length != 2){
+            logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
+          } else {
+            ++referenceNo;
+            joins += '    JOIN ${keyParts[0]} t$referenceNo ON '
+                't$referenceNo.${keyParts[1]}=t0.${property.columnName}';
+            selects += ',t$referenceNo.${parts[1]} AS ${parts[2]}';
+          }
+        }
+      }
+    }
+    final rc = sqlText.replaceFirst('JOINS', joins).replaceFirst('SELECTS', selects);
+    return rc;
+  }
 }
diff --git a/metatool/lib/base b/metatool/lib/base
new file mode 120000 (symlink)
index 0000000..b23c242
--- /dev/null
@@ -0,0 +1 @@
+../../lib/base
\ No newline at end of file
diff --git a/metatool/lib/meta b/metatool/lib/meta
new file mode 120000 (symlink)
index 0000000..09814ba
--- /dev/null
@@ -0,0 +1 @@
+../../lib/meta/
\ No newline at end of file
diff --git a/metatool/rest_server/data/sql/roles.sql.yaml b/metatool/rest_server/data/sql/roles.sql.yaml
new file mode 100644 (file)
index 0000000..3f68681
--- /dev/null
@@ -0,0 +1,32 @@
+---
+# DO NOT CHANGE. This file is created by the meta_tool
+# SQL statements of the module "Roles":
+
+module: Roles
+list:
+  type: list
+  parameters: []
+  sql: "SELECT
+    t0.*
+    FROM Roles t0
+
+    ;"
+byId:
+  type: record
+  parameters: [ "id" ]
+  sql: "SELECT * FROM Roles WHERE role_id=:id;"
+delete:
+  type: delete
+  parameters: [ "id" ]
+  sql: "DELETE * FROM Roles WHERE role_id=:id;"
+update:
+  type: update
+  parameters: [":id",":name",":changedBy"]
+  sql: "UPDATE Roles SET
+    role_id=:id,role_name=:name,role_changedby=:changedBy,role_changed=NOW()
+    WHERE role_id=:id;"
+insert:
+  type: insert
+  parameters: [":id",":name",":createdBy"]
+  sql: "INSERT INTO Roles(role_id,role_name,role_createdby,role_created)
+    VALUES(:id,:name,:createdBy,NOW());"
diff --git a/metatool/rest_server/data/sql/users.sql.yaml b/metatool/rest_server/data/sql/users.sql.yaml
new file mode 100644 (file)
index 0000000..85a06af
--- /dev/null
@@ -0,0 +1,34 @@
+---
+# DO NOT CHANGE. This file is created by the meta_tool
+# SQL statements of the module "Users":
+
+module: Users
+list:
+  type: list
+  parameters: []
+  sql: "SELECT
+    t0.*,t1.role_name AS role
+    FROM Users t0
+    JOIN roles t1 ON t1.role_id=t0.user_role
+    ;"
+byId:
+  type: record
+  parameters: [ "id" ]
+  sql: "SELECT * FROM Users WHERE user_id=:id;"
+delete:
+  type: delete
+  parameters: [ "id" ]
+  sql: "DELETE * FROM Users WHERE user_id=:id;"
+update:
+  type: update
+  parameters: [":id",":name",":displayName",":email",":role",":changedBy"]
+  sql: "UPDATE Users SET
+    user_id=:id,user_name=:name,user_displayname=:displayName,user_email=:email,
+    user_role=:role,user_changedby=:changedBy,user_changed=NOW()
+    WHERE user_id=:id;"
+insert:
+  type: insert
+  parameters: [":id",":name",":displayName",":email",":role",":createdBy"]
+  sql: "INSERT INTO Users(user_id,user_name,user_displayname,user_email,
+      user_role,user_createdby,user_created)
+    VALUES(:id,:name,:displayName,:email,:role,:createdBy,NOW());"
diff --git a/rest_client/lib/base b/rest_client/lib/base
new file mode 120000 (symlink)
index 0000000..8396cec
--- /dev/null
@@ -0,0 +1 @@
+../../lib/base/
\ No newline at end of file
index c6f196b9126567315253baf582b711f55f5a4b40..a048862272926d1bd3d34bf32567a2d9e306b5f4 100644 (file)
@@ -26,27 +26,31 @@ void main() async {
   group('query', () {
     Map<String, String> data;
     test('list', () async {
-      data = {'module': 'Persons', 'sql': 'list'};
+      data = {
+        'module': 'Persons',
+        'sql': 'list',
+        'offset': '0',
+        'size': '10',
+      };
       var result = await client.query(what: 'query', data: data);
-      expect(result, isNotNull);
-      expect(result is Iterable, isTrue);
-      expect(result.length, greaterThan(2));
-      var record = result[0];
+      expect(result.recordList, isNotNull);
+      expect(result.recordList is Iterable, isTrue);
+      expect(result.recordList!.length, greaterThan(2));
+      var record = result.recordList![0];
       expect(record['person_id'], 11);
       expect(record['person_name'], 'Jones');
-      record = result[1];
+      record = result.recordList![1];
       expect(record['person_id'], 12);
       expect(record['person_name'], 'Miller');
-      record = result[2];
+      record = result.recordList![2];
       expect(record['person_id'], 13);
       expect(record['person_name'], 'Smith');
     });
     test('record', () async {
       data = {'module': 'Persons', 'sql': 'byId', ':id': '13'};
       var result = await client.query(what: 'query', data: data);
-      expect(result, isNotNull);
-      expect(result, isNotEmpty);
-      expect(result['person_id'], 13);
+      expect(result.singleRecord, isNotNull);
+      expect(result.singleRecord!['person_id'], 13);
     });
   });
   group('store', () {
@@ -73,7 +77,7 @@ void main() async {
         ':id': id.toString(),
         ':name': 'Bach',
         ':email': 'bach@wien.at',
-        ':changedby': 'unittest'
+        ':changedby': 'unittest',
       };
       var result = await client.store(what: 'store', map: parameters);
       expect(result, isNotNull);
@@ -82,11 +86,10 @@ void main() async {
       expect(match!.group(1)!, '1');
       final data2 = {'module': 'Persons', 'sql': 'byId', ':id': id.toString()};
       var result2 = await client.query(what: 'query', data: data2);
-      expect(result2, isNotNull);
-      expect(result2, isNotEmpty);
-      expect(result2['person_id'], id);
-      expect(result2['person_name'], 'Bach');
-      expect(result2['person_email'], 'bach@wien.at');
+      expect(result2.singleRecord, isNotNull);
+      expect(result2.singleRecord!['person_id'], id);
+      expect(result2.singleRecord!['person_name'], 'Bach');
+      expect(result2.singleRecord!['person_email'], 'bach@wien.at');
     });
     test('delete', () async {
       parameters = {
index e1ad906cf71cd4c27ec239f23f0ed7abac307962..3f6868127e520ad5976db58b36028bd044c833f4 100644 (file)
@@ -6,15 +6,19 @@ module: Roles
 list:
   type: list
   parameters: []
-  sql: "select * from Roles;"
+  sql: "SELECT
+    t0.*
+    FROM Roles t0
+
+    ;"
 byId:
   type: record
   parameters: [ "id" ]
-  sql: "select * from Roles where role_id=:id;"
+  sql: "SELECT * FROM Roles WHERE role_id=:id;"
 delete:
   type: delete
   parameters: [ "id" ]
-  sql: "delete * from Roles where role_id=:id;"
+  sql: "DELETE * FROM Roles WHERE role_id=:id;"
 update:
   type: update
   parameters: [":id",":name",":changedBy"]
index 72cfb38473b52b40cb95188e01b6428861e2024e..85a06af64cfd33a734b9d9aff49bab43d2f1abd6 100644 (file)
@@ -6,15 +6,19 @@ module: Users
 list:
   type: list
   parameters: []
-  sql: "select * from Users;"
+  sql: "SELECT
+    t0.*,t1.role_name AS role
+    FROM Users t0
+    JOIN roles t1 ON t1.role_id=t0.user_role
+    ;"
 byId:
   type: record
   parameters: [ "id" ]
-  sql: "select * from Users where user_id=:id;"
+  sql: "SELECT * FROM Users WHERE user_id=:id;"
 delete:
   type: delete
   parameters: [ "id" ]
-  sql: "delete * from Users where user_id=:id;"
+  sql: "DELETE * FROM Users WHERE user_id=:id;"
 update:
   type: update
   parameters: [":id",":name",":displayName",":email",":role",":changedBy"]
index 1db96728ea7db40337303f01faf417c9f16d95c6..eb66cb2ba05468b3ad92b88e09dda5c80c6271c6 100644 (file)
@@ -11,6 +11,7 @@ import 'package:path/path.dart' as path;
 import 'package:mysql1/mysql1.dart';
 
 import 'sql_storage.dart';
+
 const forbidden = 'forbidden';
 const wrongData = 'wrong data';
 const wrongParameters = 'wrong parameters';
@@ -253,8 +254,8 @@ clientSessionTimeout: 900
       final configuration = Configuration.fromFile(filename, logger);
       MySqlDb db = MySqlDb.fromConfiguration(configuration, logger);
       await db.connect();
-      if (db.hasConnection){
-        for (var filename in args){
+      if (db.hasConnection) {
+        for (var filename in args) {
           logger.log('=== script $filename:', LEVEL_DETAIL);
           rc.writeln(await db.executeScriptFile(filename));
         }
@@ -576,10 +577,30 @@ class ServiceWorker {
           ? 'NONE'
           : rowToJson(record, StringBuffer()).toString();
     } else {
-      final records = await db!.readAll(sql, params: positionalParameters);
+      String sql2;
+      String prefix = '';
+      if (!(parameters.containsKey('offset') &&
+          parameters.containsKey('size'))) {
+        sql2 = sql;
+      } else {
+        final ix = sql.lastIndexOf(';');
+        sql2 = sql.substring(0, ix) +
+            ' limit ${parameters["offset"]},${parameters["size"]};';
+        final sqlCount =
+            'SELECT count(*) ' + sql.substring(sql.indexOf('  FROM'));
+        final countParams = '?'.allMatches(sqlCount).length;
+        final positionalParameters2 = countParams == positionalParameters.length
+            ? positionalParameters
+            : positionalParameters
+                .skip(positionalParameters.length - countParams)
+                .toList();
+        prefix =
+            '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}';
+      }
+      final records = await db!.readAll(sql2, params: positionalParameters);
       rc = records == null
           ? 'NONE'
-          : arrayToJson(records, StringBuffer()).toString();
+          : arrayToJson(records, StringBuffer(prefix)).toString();
     }
     return rc;
   }
index 3f49d1f8832f2297e22eb069a6af58d3b28b2641..d952b652f78460163d51a4a27ab47fec174b2fe7 100644 (file)
@@ -64,7 +64,8 @@ module: Persons
 list:
   type: list
   parameters: []
-  sql: select * from persons;
+  sql: "select *
+    FROM persons t0;
 byId:
   type: record
   parameters: [ ":id" ]