diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index bb29d5de6..8051a465d 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -120,14 +120,14 @@ jobs: run: dart analyze # Angel3 ORM Test - - id: angel3_orm_test_upgrade - name: angel3_orm_test; Upgrade depedencies - working-directory: packages/orm/angel_orm_test - run: dart pub upgrade + #- id: angel3_orm_test_upgrade + # name: angel3_orm_test; Upgrade depedencies + # working-directory: packages/orm/angel_orm_test + # run: dart pub upgrade - - name: angel3_orm_test; Run orm code generator - working-directory: packages/orm/angel_orm_test - run: dart run build_runner build --delete-conflicting-outputs + #- name: angel3_orm_test; Run orm code generator + # working-directory: packages/orm/angel_orm_test + # run: dart run build_runner build --delete-conflicting-outputs #- name: angel3_orm_test; Verify formatting # working-directory: packages/orm/angel_orm_test diff --git a/CHANGELOG.md b/CHANGELOG.md index deb3abdd4..62d6aab98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Changes -- Require Dart >= 3.5 +- Require Dart >= 3.6 - Updated `lints` to 5.0.0 - Updated dependencies to the latest release diff --git a/TODO.md b/TODO.md index b43593931..f59abf4d3 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,10 @@ # Development Blueprint +## TODO + +* Fix ORM generator issue +* Fix MySQL ORM issue + ## Short Term Goal * Update examples diff --git a/doc/deployment/docker/README.md b/doc/deployment/docker/README.md index 9b9b201d0..0d7c3e5ef 100644 --- a/doc/deployment/docker/README.md +++ b/doc/deployment/docker/README.md @@ -1,36 +1,38 @@ -# Docker Services +# Running as Container Services -The required applications by the framework can be run using the docker compose files provided in this folder. +The required applications by the framework can be run using the Docker compose files provided in this folder. Replace `nerdctl` with `docker` if using Docker. -## PostreSQL +## Installation -### Starting the PostreSQL container +### PostreSQL + +* Starting the PostreSQL container ```bash - docker compose -f docker-compose-pg.yml -p pg up -d + nerdctl compose -f docker-compose-pg.yml -p pg up -d ``` -### Stopping the PostreSQL container +* Stopping the PostreSQL container ```bash - docker compose -f docker-compose-pg.yml -p pg stop - docker compose -f docker-compose-pg.yml -p pg down + nerdctl compose -f docker-compose-pg.yml -p pg stop + nerdctl compose -f docker-compose-pg.yml -p pg down ``` -### Checking the PostreSQL container log +* Checking the PostreSQL container log ```bash - docker logs docker-pg-1 -f + nerdctl logs docker-pg-1 -f ``` -### Running psql +* Running psql ```bash - docker exec -it /bin/bash + nerdctl exec -it /bin/bash psql --username postgres ``` -### Create PostgreSQL database, user and grant access +* Create PostgreSQL database, user and grant access ```sql create database orm_test; @@ -38,28 +40,28 @@ The required applications by the framework can be run using the docker compose f grant all privileges on database orm_test to test; ``` -## MariaDB +### MariaDB -### Starting the MariaDB container +* Starting the MariaDB container ```bash - docker compose -f docker-compose-mariadb.yml -p maria up -d + nerdctl compose -f docker-compose-mariadb.yml -p maria up -d ``` -### Stopping the MariaDB container +* Stopping the MariaDB container ```bash - docker compose -f docker-compose-mariadb.yml -p maria stop - docker compose -f docker-compose-mariadb.yml -p maria down + nerdctl compose -f docker-compose-mariadb.yml -p maria stop + nerdctl compose -f docker-compose-mariadb.yml -p maria down ``` -### Checking the MariaDB container log +* Checking the MariaDB container log ```bash - docker logs maria-mariadb-1 -f + nerdctl logs maria-mariadb-1 -f ``` -### Create MariaDB database, user and grant access +* Create MariaDB database, user and grant access ```sql create database orm_test; @@ -73,28 +75,28 @@ The required applications by the framework can be run using the docker compose f grant all privileges on orm_test.* to 'test'@'%'; ``` -## MySQL +### MySQL -### Starting the MySQL container +* Starting the MySQL container ```bash - docker compose -f docker-compose-mysql.yml -p mysql up -d + nerdctl compose -f docker-compose-mysql.yml -p mysql up -d ``` -### Stopping the MySQL container +* Stopping the MySQL container ```bash - docker compose -f docker-compose-mysql.yml -p mysql stop - docker compose -f docker-compose-mysql.yml -p mysql down + nerdctl compose -f docker-compose-mysql.yml -p mysql stop + nerdctl compose -f docker-compose-mysql.yml -p mysql down ``` -### Checking the MySQL container log +* Checking the MySQL container log ```bash - docker logs mysql-mysql-1 -f + nerdctl logs mysql-mysql-1 -f ``` -### Create MySQL database, user and grant access +* Create MySQL database, user and grant access ```sql create database orm_test; @@ -108,65 +110,65 @@ The required applications by the framework can be run using the docker compose f grant all privileges on orm_test.* to 'test'@'%'; ``` -## MongoDB +### MongoDB -### Starting the MongoDB container +* Starting the MongoDB container ```bash - docker compose -f docker-compose-mongo.yml -p mongo up -d + nerdctl compose -f docker-compose-mongo.yml -p mongo up -d ``` -### Stopping the MongoDB container +* Stopping the MongoDB container ```bash - docker compose -f docker-compose-mongo.yml -p mongo stop - docker compose -f docker-compose-mongo.yml -p mongo down + nerdctl compose -f docker-compose-mongo.yml -p mongo stop + nerdctl compose -f docker-compose-mongo.yml -p mongo down ``` -### Checking the MongoDB container log +* Checking the MongoDB container log ```bash - docker logs mongo-mongo-1 -f + nerdctl logs mongo-mongo-1 -f ``` -## rethinkDB +### rethinkDB -### Starting the rethinkDB container +* Starting the rethinkDB container ```bash - docker compose -f docker-compose-rethinkdb.yml -p rethink up -d + nerdctl compose -f docker-compose-rethinkdb.yml -p rethink up -d ``` -### Stopping the rethinkDB container +* Stopping the rethinkDB container ```bash - docker compose -f docker-compose-rethinkdb.yml -p rethink stop - docker compose -f docker-compose-rethinkdb.yml -p rethink down + nerdctl compose -f docker-compose-rethinkdb.yml -p rethink stop + nerdctl compose -f docker-compose-rethinkdb.yml -p rethink down ``` -### Checking the rethinkDB container log +* Checking the rethinkDB container log ```bash - docker logs rethink-rethinkdb-1 -f + nerdctl logs rethink-rethinkdb-1 -f ``` -## Redis +### Redis -### Starting the Redis container +* Starting the Redis container ```bash - docker compose -f docker-compose-redis.yml -p redis up -d + nerdctl compose -f docker-compose-redis.yml -p redis up -d ``` -### Stopping the Redis container +* Stopping the Redis container ```bash - docker compose -f docker-compose-redis.yml -p redis stop - docker compose -f docker-compose-redis.yml -p redis down + nerdctl compose -f docker-compose-redis.yml -p redis stop + nerdctl compose -f docker-compose-redis.yml -p redis down ``` -### Checking the Redis container log +* Checking the Redis container log ```bash - docker logs redis-redis-1 -f + nerdctl logs redis-redis-1 -f ``` diff --git a/doc/deployment/docker/docker-compose-mariadb.yml b/doc/deployment/docker/docker-compose-mariadb.yml index 965399557..518ebbb85 100644 --- a/doc/deployment/docker/docker-compose-mariadb.yml +++ b/doc/deployment/docker/docker-compose-mariadb.yml @@ -6,14 +6,8 @@ services: - "3306:3306" environment: - MARIADB_ROOT_PASSWORD=Qwerty - volumes: - - "mariadb:/var/lib/mysql" networks: - appnet -volumes: - mariadb: - driver: local - networks: appnet: diff --git a/doc/deployment/docker/docker-compose-mongo.yml b/doc/deployment/docker/docker-compose-mongo.yml index 6e876e666..8175f8d8e 100644 --- a/doc/deployment/docker/docker-compose-mongo.yml +++ b/doc/deployment/docker/docker-compose-mongo.yml @@ -9,8 +9,6 @@ services: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: Qwerty MONGO_INITDB_DATABASE: local - volumes: - - "mongo:/data/db" networks: - appnet @@ -27,11 +25,7 @@ services: ME_CONFIG_MONGODB_URL: mongodb://root:Qwerty@mongo:27017/ ME_CONFIG_BASICAUTH: false networks: - - webnet - -volumes: - mongo: - driver: local + - appnet networks: appnet: diff --git a/doc/deployment/docker/docker-compose-mysql.yml b/doc/deployment/docker/docker-compose-mysql.yml index 90c187f05..dd5c58966 100644 --- a/doc/deployment/docker/docker-compose-mysql.yml +++ b/doc/deployment/docker/docker-compose-mysql.yml @@ -6,14 +6,17 @@ services: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=Qwerty - volumes: - - "mysql:/var/lib/mysql" + - MYSQL_DATABASE=orm_test + - MYSQL_USER=test + - MYSQL_PASSWORD=Test123 +# volumes: +# - C://storage/mysql:/var/lib/mysql:ro networks: - appnet -volumes: - mysql: - driver: local +#volumes: +# mysql-data-external: +# driver: local networks: appnet: diff --git a/doc/deployment/docker/docker-compose-pg.yml b/doc/deployment/docker/docker-compose-pg.yml index e89b67aef..9f9ada5b3 100644 --- a/doc/deployment/docker/docker-compose-pg.yml +++ b/doc/deployment/docker/docker-compose-pg.yml @@ -7,8 +7,6 @@ services: environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - volumes: - - "db:/var/lib/postgresql/data" networks: - appnet @@ -23,9 +21,5 @@ services: networks: - appnet -volumes: - db: - driver: local - networks: appnet: diff --git a/doc/deployment/docker/docker-compose-redis.yml b/doc/deployment/docker/docker-compose-redis.yml index e977709c1..d362a4975 100644 --- a/doc/deployment/docker/docker-compose-redis.yml +++ b/doc/deployment/docker/docker-compose-redis.yml @@ -7,14 +7,8 @@ services: environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - volumes: - - "redis:/data" networks: - appnet -volumes: - redis: - driver: local - networks: appnet: diff --git a/doc/deployment/docker/docker-compose-rethinkdb.yml b/doc/deployment/docker/docker-compose-rethinkdb.yml index 693133b5d..d900e1668 100644 --- a/doc/deployment/docker/docker-compose-rethinkdb.yml +++ b/doc/deployment/docker/docker-compose-rethinkdb.yml @@ -6,14 +6,8 @@ services: - "8080:8080" - "28015:28015" - "29015:29015" - volumes: - - "rethinkdb:/data" networks: - appnet -volumes: - rethinkdb: - driver: local - networks: appnet: diff --git a/packages/auth/CHANGELOG.md b/packages/auth/CHANGELOG.md index f46eda19f..05ef5f63b 100644 --- a/packages/auth/CHANGELOG.md +++ b/packages/auth/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/auth/lib/angel3_auth.dart b/packages/auth/lib/angel3_auth.dart index 95d4fc9a9..68d6eab71 100644 --- a/packages/auth/lib/angel3_auth.dart +++ b/packages/auth/lib/angel3_auth.dart @@ -1,4 +1,4 @@ -library angel3_auth; +library; export 'src/middleware/require_auth.dart'; export 'src/strategies/strategies.dart'; diff --git a/packages/auth/lib/auth_token.dart b/packages/auth/lib/auth_token.dart index b1db07a0c..01af09003 100644 --- a/packages/auth/lib/auth_token.dart +++ b/packages/auth/lib/auth_token.dart @@ -1,4 +1,4 @@ /// Stand-alone JWT library. -library angel3_auth.auth_token; +library; export 'src/auth_token.dart'; diff --git a/packages/auth/pubspec.yaml b/packages/auth/pubspec.yaml index 966bc3276..e672466fe 100644 --- a/packages/auth/pubspec.yaml +++ b/packages/auth/pubspec.yaml @@ -4,7 +4,7 @@ version: 8.3.0 homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/auth environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 charcode: ^1.3.0 diff --git a/packages/auth_oauth2/CHANGELOG.md b/packages/auth_oauth2/CHANGELOG.md index de1df625a..b20f837bc 100644 --- a/packages/auth_oauth2/CHANGELOG.md +++ b/packages/auth_oauth2/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/auth_oauth2/lib/angel3_auth_oauth2.dart b/packages/auth_oauth2/lib/angel3_auth_oauth2.dart index 6a16c6adb..ecb9b98a9 100644 --- a/packages/auth_oauth2/lib/angel3_auth_oauth2.dart +++ b/packages/auth_oauth2/lib/angel3_auth_oauth2.dart @@ -1,4 +1,4 @@ -library angel3_auth_oauth2; +library; import 'dart:async'; import 'package:angel3_auth/angel3_auth.dart'; diff --git a/packages/auth_oauth2/pubspec.yaml b/packages/auth_oauth2/pubspec.yaml index 9204aed3f..80b45f6f3 100644 --- a/packages/auth_oauth2/pubspec.yaml +++ b/packages/auth_oauth2/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 library for authenticating users with external identity prov homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/auth_oauth2 environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_auth: ^8.2.0 angel3_framework: ^8.4.0 diff --git a/packages/auth_twitter/CHANGELOG.md b/packages/auth_twitter/CHANGELOG.md index afe772be1..2df294d0f 100644 --- a/packages/auth_twitter/CHANGELOG.md +++ b/packages/auth_twitter/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.0.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `oauth1` to `belatuk_oauth1` * Updated `lints` to 5.0.0 * Updated repository link diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md index 8bd11e9ab..a9a11af63 100644 --- a/packages/cache/CHANGELOG.md +++ b/packages/cache/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 1f828a8c5..529ca7bf4 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release * Updated error handling diff --git a/packages/client/lib/angel3_client.dart b/packages/client/lib/angel3_client.dart index 853dc9038..9580fb52a 100644 --- a/packages/client/lib/angel3_client.dart +++ b/packages/client/lib/angel3_client.dart @@ -1,5 +1,5 @@ /// Client library for the Angel framework. -library angel3_client; +library; import 'dart:async'; import 'package:collection/collection.dart'; diff --git a/packages/client/lib/browser.dart b/packages/client/lib/browser.dart index 941fbbb7c..fedb784df 100644 --- a/packages/client/lib/browser.dart +++ b/packages/client/lib/browser.dart @@ -1,5 +1,5 @@ /// Browser client library for the Angel framework. -library angel_client.browser; +library; import 'dart:async' show Future, Stream, StreamController, StreamSubscription, Timer; diff --git a/packages/client/lib/flutter.dart b/packages/client/lib/flutter.dart index 0a2bc2cd8..59ef3d3b6 100644 --- a/packages/client/lib/flutter.dart +++ b/packages/client/lib/flutter.dart @@ -1,5 +1,5 @@ /// Flutter-compatible client library for the Angel framework. -library angel_client.flutter; +library; import 'dart:async'; import 'package:http/http.dart' as http; diff --git a/packages/client/lib/io.dart b/packages/client/lib/io.dart index 49ab641a0..47001bc42 100644 --- a/packages/client/lib/io.dart +++ b/packages/client/lib/io.dart @@ -1,5 +1,5 @@ /// Command-line client library for the Angel framework. -library angel_client.cli; +library; import 'dart:async'; import 'package:http/http.dart' as http; diff --git a/packages/client/pubspec.yaml b/packages/client/pubspec.yaml index 8f5dfc564..77392b4f8 100644 --- a/packages/client/pubspec.yaml +++ b/packages/client/pubspec.yaml @@ -4,7 +4,7 @@ description: A browser, mobile and command line based client that supports query homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/client environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_http_exception: ^8.0.0 belatuk_json_serializer: ^7.0.0 diff --git a/packages/configuration/CHANGELOG.md b/packages/configuration/CHANGELOG.md index 1fbf63ded..16b57b3cd 100644 --- a/packages/configuration/CHANGELOG.md +++ b/packages/configuration/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/configuration/lib/angel3_configuration.dart b/packages/configuration/lib/angel3_configuration.dart index 8dac2876b..5422e6fc9 100644 --- a/packages/configuration/lib/angel3_configuration.dart +++ b/packages/configuration/lib/angel3_configuration.dart @@ -1,4 +1,4 @@ -library angel3_configuration; +library; import 'dart:async'; diff --git a/packages/configuration/pubspec.yaml b/packages/configuration/pubspec.yaml index bf011e78d..7d3af54b1 100644 --- a/packages/configuration/pubspec.yaml +++ b/packages/configuration/pubspec.yaml @@ -4,7 +4,7 @@ description: Automatic YAML application configuration loader for Angel 3, with . homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/configuration environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 belatuk_merge_map: ^5.0.0 diff --git a/packages/container/angel_container/CHANGELOG.md b/packages/container/angel_container/CHANGELOG.md index fd892aa8f..2d9d19c44 100644 --- a/packages/container/angel_container/CHANGELOG.md +++ b/packages/container/angel_container/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/container/angel_container/lib/angel3_container.dart b/packages/container/angel_container/lib/angel3_container.dart index e50508e83..b3edb8bca 100644 --- a/packages/container/angel_container/lib/angel3_container.dart +++ b/packages/container/angel_container/lib/angel3_container.dart @@ -1,4 +1,4 @@ -library angel3_container; +library; export 'src/container.dart'; export 'src/empty/empty.dart'; diff --git a/packages/container/angel_container/pubspec.yaml b/packages/container/angel_container/pubspec.yaml index 592f59017..3a4f5a32e 100644 --- a/packages/container/angel_container/pubspec.yaml +++ b/packages/container/angel_container/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 hierarchical DI container, and pluggable backends for reflec homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/container/angel_container environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: collection: ^1.17.0 quiver: ^3.2.0 diff --git a/packages/container/angel_container_generator/CHANGELOG.md b/packages/container/angel_container_generator/CHANGELOG.md index fbebdb47d..3d87a6fd8 100644 --- a/packages/container/angel_container_generator/CHANGELOG.md +++ b/packages/container/angel_container_generator/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/container/angel_container_generator/pubspec.yaml b/packages/container/angel_container_generator/pubspec.yaml index 330bb6046..847c101a7 100644 --- a/packages/container/angel_container_generator/pubspec.yaml +++ b/packages/container/angel_container_generator/pubspec.yaml @@ -4,7 +4,7 @@ description: Codegen support for using pkg:reflectable with pkg:angel3_container homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/container/angel_container_generator environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_container: ^8.0.0 reflectable: ^4.0.0 diff --git a/packages/cors/CHANGELOG.md b/packages/cors/CHANGELOG.md index 375148699..2818004bd 100644 --- a/packages/cors/CHANGELOG.md +++ b/packages/cors/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/cors/lib/angel3_cors.dart b/packages/cors/lib/angel3_cors.dart index c21516cc5..ce71ee58a 100644 --- a/packages/cors/lib/angel3_cors.dart +++ b/packages/cors/lib/angel3_cors.dart @@ -1,5 +1,5 @@ /// Angel CORS middleware. -library angel3_cors; +library; import 'dart:async'; diff --git a/packages/cors/pubspec.yaml b/packages/cors/pubspec.yaml index 841f83dd3..281d2dd5d 100644 --- a/packages/cors/pubspec.yaml +++ b/packages/cors/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 CORS middleware. Ported from expressjs/cors to Angel3 framew homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/cors environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 dev_dependencies: diff --git a/packages/file_service/CHANGELOG.md b/packages/file_service/CHANGELOG.md index 4e951d355..43e32d1e5 100644 --- a/packages/file_service/CHANGELOG.md +++ b/packages/file_service/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/file_service/pubspec.yaml b/packages/file_service/pubspec.yaml index 55e0661ce..8295764d8 100644 --- a/packages/file_service/pubspec.yaml +++ b/packages/file_service/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel service that persists data to a file on disk, stored as a JSO homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/file_service environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 file: ^7.0.0 diff --git a/packages/framework/CHANGELOG.md b/packages/framework/CHANGELOG.md index 76d543799..f50b83439 100644 --- a/packages/framework/CHANGELOG.md +++ b/packages/framework/CHANGELOG.md @@ -2,9 +2,10 @@ ## 8.5.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 -* res.json() will cause 'Bad state: Cannot modify a closed response.' error. +* Updated `mime` to 2.0.0 +* Fixed res.json() will cause 'Bad state: Cannot modify a closed response.' error. ## 8.4.0 diff --git a/packages/framework/lib/angel3_framework.dart b/packages/framework/lib/angel3_framework.dart index 529046cb7..60b9efbe5 100644 --- a/packages/framework/lib/angel3_framework.dart +++ b/packages/framework/lib/angel3_framework.dart @@ -1,5 +1,5 @@ /// An easily-extensible web server framework in Dart. -library angel3_framework; +library; export 'package:angel3_http_exception/angel3_http_exception.dart'; export 'package:angel3_model/angel3_model.dart'; diff --git a/packages/framework/lib/src/core/controller.dart b/packages/framework/lib/src/core/controller.dart index 5fb6a1318..5e7311093 100644 --- a/packages/framework/lib/src/core/controller.dart +++ b/packages/framework/lib/src/core/controller.dart @@ -1,4 +1,4 @@ -library angel_framework.http.controller; +library; import 'dart:async'; import 'package:angel3_container/angel3_container.dart'; diff --git a/packages/framework/lib/src/core/hooked_service.dart b/packages/framework/lib/src/core/hooked_service.dart index dbb5e6a62..891632f5e 100644 --- a/packages/framework/lib/src/core/hooked_service.dart +++ b/packages/framework/lib/src/core/hooked_service.dart @@ -1,4 +1,4 @@ -library angel_framework.core.hooked_service; +library; import 'dart:async'; diff --git a/packages/framework/lib/src/core/metadata.dart b/packages/framework/lib/src/core/metadata.dart index a96aac5ad..adebbeb8c 100644 --- a/packages/framework/lib/src/core/metadata.dart +++ b/packages/framework/lib/src/core/metadata.dart @@ -1,4 +1,4 @@ -library angel_framework.http.metadata; +library; import 'package:angel3_http_exception/angel3_http_exception.dart'; diff --git a/packages/framework/lib/src/core/request_context.dart b/packages/framework/lib/src/core/request_context.dart index 19499af3c..9aa6d926f 100644 --- a/packages/framework/lib/src/core/request_context.dart +++ b/packages/framework/lib/src/core/request_context.dart @@ -1,4 +1,4 @@ -library angel_framework.http.request_context; +library; import 'dart:async'; import 'dart:convert'; diff --git a/packages/framework/lib/src/core/response_context.dart b/packages/framework/lib/src/core/response_context.dart index 97793880e..7e09e874b 100644 --- a/packages/framework/lib/src/core/response_context.dart +++ b/packages/framework/lib/src/core/response_context.dart @@ -1,4 +1,4 @@ -library angel_framework.http.response_context; +library; import 'dart:async'; import 'dart:convert'; diff --git a/packages/framework/lib/src/core/routable.dart b/packages/framework/lib/src/core/routable.dart index 5543f1abb..50b0c3b92 100644 --- a/packages/framework/lib/src/core/routable.dart +++ b/packages/framework/lib/src/core/routable.dart @@ -1,4 +1,4 @@ -library angel_framework.http.routable; +library; import 'dart:async'; diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index c0f1597e9..ce6bccbaa 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -1,4 +1,4 @@ -library angel_framework.http.server; +library; import 'dart:async'; import 'dart:collection' show HashMap; diff --git a/packages/framework/lib/src/core/service.dart b/packages/framework/lib/src/core/service.dart index 8db6996c7..2fcd31bfa 100644 --- a/packages/framework/lib/src/core/service.dart +++ b/packages/framework/lib/src/core/service.dart @@ -1,4 +1,4 @@ -library angel_framework.http.service; +library; import 'dart:async'; import 'package:angel3_http_exception/angel3_http_exception.dart'; diff --git a/packages/framework/lib/src/http/http.dart b/packages/framework/lib/src/http/http.dart index d4ea40b16..e59104ebb 100644 --- a/packages/framework/lib/src/http/http.dart +++ b/packages/framework/lib/src/http/http.dart @@ -1,5 +1,5 @@ /// Various libraries useful for creating highly-extensible servers. -library angel_framework.http; +library; import 'dart:async'; import 'dart:io'; diff --git a/packages/framework/performance/hello/main.dart b/packages/framework/performance/hello/main.dart index 8f8e559f1..2cd2e80fe 100644 --- a/packages/framework/performance/hello/main.dart +++ b/packages/framework/performance/hello/main.dart @@ -1,5 +1,5 @@ /// A basic server that prints "Hello, world!" -library performance.hello; +library; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_framework/http.dart'; diff --git a/packages/framework/performance/hello/raw.dart b/packages/framework/performance/hello/raw.dart index 33a08ffce..8ce4d2918 100644 --- a/packages/framework/performance/hello/raw.dart +++ b/packages/framework/performance/hello/raw.dart @@ -1,5 +1,5 @@ /// A basic server that prints "Hello, world!" -library performance.hello; +library; import 'dart:io'; diff --git a/packages/framework/pubspec.yaml b/packages/framework/pubspec.yaml index 1c010c8f3..cdf1837f8 100644 --- a/packages/framework/pubspec.yaml +++ b/packages/framework/pubspec.yaml @@ -4,7 +4,7 @@ description: A high-powered HTTP server extensible framework with dependency inj homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/framework environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_container: ^8.0.0 angel3_http_exception: ^8.0.0 @@ -21,7 +21,7 @@ dependencies: logging: ^1.1.0 matcher: ^0.12.16 meta: ^1.9.0 - mime: ^1.0.0 + mime: ^2.0.0 path: ^1.8.0 quiver: ^3.2.0 recase: ^4.1.0 diff --git a/packages/framework/test/common.dart b/packages/framework/test/common.dart index 2de9b70f9..2f981af79 100644 --- a/packages/framework/test/common.dart +++ b/packages/framework/test/common.dart @@ -1,4 +1,4 @@ -library angel_framework.test.common; +library; import 'package:angel3_framework/angel3_framework.dart'; import 'package:matcher/matcher.dart'; diff --git a/packages/hot/CHANGELOG.md b/packages/hot/CHANGELOG.md index 86787074f..bac113122 100644 --- a/packages/hot/CHANGELOG.md +++ b/packages/hot/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/hot/pubspec.yaml b/packages/hot/pubspec.yaml index ef0bde8df..0ba7caf03 100644 --- a/packages/hot/pubspec.yaml +++ b/packages/hot/pubspec.yaml @@ -4,7 +4,7 @@ description: Supports hot reloading/hot code push of Angel3 servers on file chan homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/hot environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_websocket: ^8.2.0 diff --git a/packages/html/CHANGELOG.md b/packages/html/CHANGELOG.md index 5124e471e..482266673 100644 --- a/packages/html/CHANGELOG.md +++ b/packages/html/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.2.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/html/pubspec.yaml b/packages/html/pubspec.yaml index d93faa79a..098d21643 100644 --- a/packages/html/pubspec.yaml +++ b/packages/html/pubspec.yaml @@ -4,7 +4,7 @@ description: Support for rendering html_builder AST's as responses in Angel. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/angel3/packages/html_builder environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 belatuk_html_builder: ^5.0.0 diff --git a/packages/http_exception/CHANGELOG.md b/packages/http_exception/CHANGELOG.md index 330c5e14f..3dfea51f4 100644 --- a/packages/http_exception/CHANGELOG.md +++ b/packages/http_exception/CHANGELOG.md @@ -3,7 +3,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/http_exception/lib/angel3_http_exception.dart b/packages/http_exception/lib/angel3_http_exception.dart index e64a20162..0e4809cde 100644 --- a/packages/http_exception/lib/angel3_http_exception.dart +++ b/packages/http_exception/lib/angel3_http_exception.dart @@ -1,4 +1,4 @@ -library angel3_http_exception; +library; //import 'package:dart2_constant/convert.dart'; import 'dart:convert'; diff --git a/packages/http_exception/pubspec.yaml b/packages/http_exception/pubspec.yaml index 6c34d57df..06218359c 100644 --- a/packages/http_exception/pubspec.yaml +++ b/packages/http_exception/pubspec.yaml @@ -4,6 +4,6 @@ description: Exception class that can be serialized to JSON and serialized to cl homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/http_exception environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dev_dependencies: lints: ^5.0.0 \ No newline at end of file diff --git a/packages/jael/angel_jael/CHANGELOG.md b/packages/jael/angel_jael/CHANGELOG.md index c34ae2607..4dc0d5f35 100644 --- a/packages/jael/angel_jael/CHANGELOG.md +++ b/packages/jael/angel_jael/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/jael/angel_jael/pubspec.yaml b/packages/jael/angel_jael/pubspec.yaml index 2dc15764f..584b719fc 100644 --- a/packages/jael/angel_jael/pubspec.yaml +++ b/packages/jael/angel_jael/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel support for the Jael templating engine, similar to Blade or L homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/jael/angel_jael environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 jael3: ^8.0.0 diff --git a/packages/jael/jael/lib/src/text/parselet/parselet.dart b/packages/jael/jael/lib/src/text/parselet/parselet.dart index 1f073c37c..6f91f673f 100644 --- a/packages/jael/jael/lib/src/text/parselet/parselet.dart +++ b/packages/jael/jael/lib/src/text/parselet/parselet.dart @@ -1,4 +1,4 @@ -library jael.src.text.parselet; +library; import '../../ast/ast.dart'; import '../parser.dart'; diff --git a/packages/jael/jael/pubspec.yaml b/packages/jael/jael/pubspec.yaml index 65e69e128..8232004c8 100644 --- a/packages/jael/jael/pubspec.yaml +++ b/packages/jael/jael/pubspec.yaml @@ -4,7 +4,7 @@ description: A simple server-side HTML templating engine for Dart. Comparable t homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/jael/jael environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: args: ^2.4.0 charcode: ^1.3.0 diff --git a/packages/jael/jael_language_server/pubspec.yaml b/packages/jael/jael_language_server/pubspec.yaml index cc96a7c97..d4bd747cc 100644 --- a/packages/jael/jael_language_server/pubspec.yaml +++ b/packages/jael/jael_language_server/pubspec.yaml @@ -4,7 +4,7 @@ description: Language Server Protocol implementation for the Jael templating eng homepage: https://github.com/angel-dart/vscode publish_to: none environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: args: ^2.4.0 # dart_language_server: ^0.1.16 diff --git a/packages/jael/jael_preprocessor/pubspec.yaml b/packages/jael/jael_preprocessor/pubspec.yaml index 5652cc7df..a8dbd9179 100644 --- a/packages/jael/jael_preprocessor/pubspec.yaml +++ b/packages/jael/jael_preprocessor/pubspec.yaml @@ -4,7 +4,7 @@ description: A pre-processor for resolving blocks and includes within Jael templ homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/jael/jael_preprocessor environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: file: ^7.0.0 jael3: ^8.0.0 diff --git a/packages/jael/jael_web/CHANGELOG.md b/packages/jael/jael_web/CHANGELOG.md index bd5f09611..d8dafaec2 100644 --- a/packages/jael/jael_web/CHANGELOG.md +++ b/packages/jael/jael_web/CHANGELOG.md @@ -2,9 +2,10 @@ ## 8.0.0 -* Require Dart >= 3.3 -* Updated `lints` to 4.0.0 -* Updated `analyzer` to 6.2.x +* Require Dart >= 3.6 +* Updated `lints` to 5.0.0 +* Updated `analyzer` to 7.x +* Updated `source_gen` to 2.0.0 * Fixed linter warnings * Updated repository link diff --git a/packages/jael/jael_web/pubspec.yaml b/packages/jael/jael_web/pubspec.yaml index 50327040d..69dfe6eac 100644 --- a/packages/jael/jael_web/pubspec.yaml +++ b/packages/jael/jael_web/pubspec.yaml @@ -3,9 +3,9 @@ version: 8.0.0 description: Experimental virtual DOM/SPA engine built on Jael3. Supports SSR. publish_to: none environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: - analyzer: ^6.2.0 + analyzer: ^7.2.0 build: ^2.0.2 build_config: ^1.0.0 code_builder: ^4.0.0 @@ -13,7 +13,7 @@ dependencies: path: ^1.8.0 jael3: ^8.0.0 jael3_preprocessor: ^8.0.0 - source_gen: ^1.3.0 + source_gen: ^2.0.0 dev_dependencies: build_runner: ^2.4.0 build_web_compilers: ^4.0.0 diff --git a/packages/jinja/CHANGELOG.md b/packages/jinja/CHANGELOG.md index f5708aa15..05385ee73 100644 --- a/packages/jinja/CHANGELOG.md +++ b/packages/jinja/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/jinja/pubspec.yaml b/packages/jinja/pubspec.yaml index b887bbc3d..cfdfbc1f1 100644 --- a/packages/jinja/pubspec.yaml +++ b/packages/jinja/pubspec.yaml @@ -3,7 +3,7 @@ version: 8.4.0 description: A service that renders Jinja2 template into HTML view for Angel3. Ported from Python to Dart. homepage: https://github.com/dart-backend/angel/tree/master/packages/jinja environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 jinja: ^0.6.0 diff --git a/packages/markdown/CHANGELOG.md b/packages/markdown/CHANGELOG.md index 8fec751f5..5b5c18352 100644 --- a/packages/markdown/CHANGELOG.md +++ b/packages/markdown/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/markdown/pubspec.yaml b/packages/markdown/pubspec.yaml index d35c92134..70008119c 100644 --- a/packages/markdown/pubspec.yaml +++ b/packages/markdown/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 Markdown view generator. Write static sites, with no build s homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/markdown environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 file: ^7.0.0 diff --git a/packages/mock_request/CHANGELOG.md b/packages/mock_request/CHANGELOG.md index f335a5cf3..819219bcd 100644 --- a/packages/mock_request/CHANGELOG.md +++ b/packages/mock_request/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/mock_request/pubspec.yaml b/packages/mock_request/pubspec.yaml index ea9772294..961da3f1c 100644 --- a/packages/mock_request/pubspec.yaml +++ b/packages/mock_request/pubspec.yaml @@ -4,7 +4,7 @@ description: Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/mock_request environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: charcode: ^1.3.0 dev_dependencies: diff --git a/packages/model/CHANGELOG.md b/packages/model/CHANGELOG.md index fb411cae6..f37aac915 100644 --- a/packages/model/CHANGELOG.md +++ b/packages/model/CHANGELOG.md @@ -2,9 +2,10 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release +* Added `None` ## 8.2.0 diff --git a/packages/model/lib/angel3_model.dart b/packages/model/lib/angel3_model.dart index 0ee8fabfc..cca7cfb16 100644 --- a/packages/model/lib/angel3_model.dart +++ b/packages/model/lib/angel3_model.dart @@ -33,3 +33,9 @@ class AuditableModel extends Model { super.updatedAt, this.updatedBy}); } + +/// Data type to represent no data. +class None extends Object {} + +/// Canonical instance of [None]. Implies no data. +const none = None; diff --git a/packages/model/pubspec.yaml b/packages/model/pubspec.yaml index 05f037e97..412a50de2 100644 --- a/packages/model/pubspec.yaml +++ b/packages/model/pubspec.yaml @@ -4,6 +4,6 @@ description: Angel3 basic data model class, no longer with the added weight of t homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/model environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dev_dependencies: lints: ^5.0.0 diff --git a/packages/mongo/CHANGELOG.md b/packages/mongo/CHANGELOG.md index d8fea6085..adf04303a 100644 --- a/packages/mongo/CHANGELOG.md +++ b/packages/mongo/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/mongo/lib/angel3_mongo.dart b/packages/mongo/lib/angel3_mongo.dart index 0b0b620aa..2d2bfffd3 100644 --- a/packages/mongo/lib/angel3_mongo.dart +++ b/packages/mongo/lib/angel3_mongo.dart @@ -1,3 +1,3 @@ -library angel3_mongo; +library; export 'services.dart'; diff --git a/packages/mongo/lib/services.dart b/packages/mongo/lib/services.dart index 4e84194ec..4755ed42c 100644 --- a/packages/mongo/lib/services.dart +++ b/packages/mongo/lib/services.dart @@ -1,4 +1,4 @@ -library angel3_mongo.services; +library; import 'dart:async'; import 'package:angel3_framework/angel3_framework.dart'; @@ -45,7 +45,9 @@ Map _filterNoQuery(Map data) { if (_noQuery.contains(key) || value is RequestContext || - value is ResponseContext) return map; + value is ResponseContext) { + return map; + } if (key is! Map) return map..[key] = value; return map..[key] = _filterNoQuery(value as Map); }); diff --git a/packages/mongo/pubspec.yaml b/packages/mongo/pubspec.yaml index b9e38fdcd..cd9b52fd3 100644 --- a/packages/mongo/pubspec.yaml +++ b/packages/mongo/pubspec.yaml @@ -4,7 +4,7 @@ description: This is MongoDB-enabled service for the Angel3 framework. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/mongo environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 diff --git a/packages/mustache/CHANGELOG.md b/packages/mustache/CHANGELOG.md index 28298be6f..d1caa634e 100644 --- a/packages/mustache/CHANGELOG.md +++ b/packages/mustache/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/mustache/lib/angel3_mustache.dart b/packages/mustache/lib/angel3_mustache.dart index c387f4c71..b4f07381b 100644 --- a/packages/mustache/lib/angel3_mustache.dart +++ b/packages/mustache/lib/angel3_mustache.dart @@ -1,4 +1,4 @@ -library angel3_mustache; +library; import 'dart:async'; diff --git a/packages/mustache/pubspec.yaml b/packages/mustache/pubspec.yaml index b0d492b5d..43387669c 100644 --- a/packages/mustache/pubspec.yaml +++ b/packages/mustache/pubspec.yaml @@ -4,7 +4,7 @@ description: A service that renders Mustache template into HTML view for Angel3 homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/mustache environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 file: ^7.0.0 diff --git a/packages/oauth2/CHANGELOG.md b/packages/oauth2/CHANGELOG.md index c24783549..04adbe0bc 100644 --- a/packages/oauth2/CHANGELOG.md +++ b/packages/oauth2/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/oauth2/pubspec.yaml b/packages/oauth2/pubspec.yaml index b9b529f7e..5bdbcd87f 100644 --- a/packages/oauth2/pubspec.yaml +++ b/packages/oauth2/pubspec.yaml @@ -4,7 +4,7 @@ description: A class containing handlers that can be used within Angel to build homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/oauth2 environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_http_exception: ^8.0.0 diff --git a/packages/orm/README.md b/packages/orm/README.md index 14efdfa62..507480af4 100644 --- a/packages/orm/README.md +++ b/packages/orm/README.md @@ -27,9 +27,9 @@ You'll need these dependencies in your `pubspec.yaml`: ```yaml dependencies: - angel3_orm: ^6.0.0 + angel3_orm: ^8.0.0 dev_dependencies: - angel3_orm_generator: ^6.0.0 + angel3_orm_generator: ^8.0.0 build_runner: ^2.0.0 ``` diff --git a/packages/orm/angel_migration/CHANGELOG.md b/packages/orm/angel_migration/CHANGELOG.md index 44cb8315c..69ddb00cf 100755 --- a/packages/orm/angel_migration/CHANGELOG.md +++ b/packages/orm/angel_migration/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/orm/angel_migration/example/main.dart b/packages/orm/angel_migration/example/main.dart index 3ae5cb493..54cf8f24b 100644 --- a/packages/orm/angel_migration/example/main.dart +++ b/packages/orm/angel_migration/example/main.dart @@ -1,8 +1,7 @@ -/// These are straightforward migrations. -/// -/// You will likely never have to actually write these yourself. -library angel3_migration.example.todo; - +/* + These are straightforward migrations. + You will likely never have to actually write these yourself. +*/ import 'package:angel3_migration/angel3_migration.dart'; class UserMigration implements Migration { diff --git a/packages/orm/angel_migration/pubspec.yaml b/packages/orm/angel_migration/pubspec.yaml index 895b1a39c..68fab7679 100755 --- a/packages/orm/angel_migration/pubspec.yaml +++ b/packages/orm/angel_migration/pubspec.yaml @@ -4,7 +4,7 @@ description: The abstract classes for implementing database migration in Angel3 homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_migration environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_orm: ^8.2.0 dev_dependencies: diff --git a/packages/orm/angel_migration_runner/CHANGELOG.md b/packages/orm/angel_migration_runner/CHANGELOG.md index eb9c8192b..5d0868401 100755 --- a/packages/orm/angel_migration_runner/CHANGELOG.md +++ b/packages/orm/angel_migration_runner/CHANGELOG.md @@ -2,9 +2,11 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release +* [MySQL] Fixed default value issue +* [MySQL] Fixed `@Column(indexType: IndexType.unique)` ## 8.3.0 @@ -12,7 +14,7 @@ ## 8.2.2 -Fixed `MariaDbMigrationRunner` migration issues +* Fixed `MariaDbMigrationRunner` migration issues ## 8.2.1 diff --git a/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart b/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart index ab18b079e..d0e00bd73 100644 --- a/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart +++ b/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart @@ -45,9 +45,7 @@ abstract class MariaDbGenerator { buf.write(' DEFAULT $s'); } - if (column.indexType == IndexType.unique) { - buf.write(' UNIQUE'); - } else if (column.indexType == IndexType.primaryKey) { + if (column.indexType == IndexType.primaryKey) { buf.write(' PRIMARY KEY'); // For int based primary key, apply NOT NULL @@ -67,6 +65,8 @@ abstract class MariaDbGenerator { static String? compileIndex(String name, MigrationColumn column) { if (column.indexType == IndexType.standardIndex) { return ' INDEX(`$name`)'; + } else if (column.indexType == IndexType.unique) { + return ' UNIQUE KEY unique_$name (`$name`)'; } return null; @@ -257,7 +257,7 @@ class MariaDbIndexes implements MutableIndexes { case IndexType.unique: _stack.add( 'CREATE UNIQUE INDEX IF NOT EXISTS `$name` ' - 'ON `$tableName` (${columns.join(',')});', + 'ON `$tableName` (${columns.join(',')});', ); break; case IndexType.standardIndex: diff --git a/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart b/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart index 44a276fca..93cb9ff3a 100644 --- a/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart +++ b/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart @@ -23,13 +23,13 @@ class MySqlSchema extends Schema { await connection.transactional((ctx) async { var sql = compile(); var result = await ctx.execute(sql).catchError((e) { - print(e); + //print(e); _log.severe('Failed to run query: [ $sql ]', e); throw Exception(e); }); affectedRows = result.affectedRows.toInt(); }).catchError((e) { - print(e); + //print(e); _log.severe('Failed to run query in a transaction', e); }); @@ -70,6 +70,8 @@ class MySqlSchema extends Schema { tbl.compile(_buf, _indent + 1); _buf.writeln(); _writeln(');'); + + _log.fine(_buf); } @override diff --git a/packages/orm/angel_migration_runner/lib/src/mysql/table.dart b/packages/orm/angel_migration_runner/lib/src/mysql/table.dart index 3f4ad6737..67ca113f6 100644 --- a/packages/orm/angel_migration_runner/lib/src/mysql/table.dart +++ b/packages/orm/angel_migration_runner/lib/src/mysql/table.dart @@ -1,10 +1,17 @@ import 'dart:collection'; +import 'dart:js_interop'; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:charcode/ascii.dart'; +/// MySQL SQL query generator abstract class MySqlGenerator { + static final List _charColumnType = [ + ColumnType.varChar.name, + ColumnType.char.name + ]; + static String columnType(MigrationColumn column) { var str = column.type.name; // Map timestamp time to datetime @@ -12,6 +19,14 @@ abstract class MySqlGenerator { str = ColumnType.dateTime.name; } + if (_charColumnType.contains(str)) { + if (column.type.hasLength) { + return '$str(${column.length})'; + } else { + return '$str(255)'; + } + } + if (column.type.hasLength) { return '$str(${column.length})'; } else { @@ -46,12 +61,14 @@ abstract class MySqlGenerator { s = value.toString(); } - buf.write(' DEFAULT $s'); + if (column.type == ColumnType.varChar) { + buf.write(' DEFAULT \'$s\''); + } else { + buf.write(' DEFAULT $s'); + } } - if (column.indexType == IndexType.unique) { - buf.write(' UNIQUE'); - } else if (column.indexType == IndexType.primaryKey) { + if (column.indexType == IndexType.primaryKey) { buf.write(' PRIMARY KEY'); // For int based primary key, apply NOT NULL @@ -71,6 +88,8 @@ abstract class MySqlGenerator { static String? compileIndex(String name, MigrationColumn column) { if (column.indexType == IndexType.standardIndex) { return ' INDEX(`$name`)'; + } else if (column.indexType == IndexType.unique) { + return ' UNIQUE KEY unique_$name (`$name`)'; } return null; @@ -119,8 +138,6 @@ class MysqlTable extends Table { buf.write(',\n${indexBuf[i]}'); } } - - print(buf); } } @@ -264,8 +281,8 @@ class MysqlIndexes implements MutableIndexes { 'CREATE UNIQUE INDEX `$name` ON `$tableName` (${columns.join(',')});', ); break; - case IndexType.standardIndex: - case IndexType.none: + //case IndexType.standardIndex: + //case IndexType.none: default: _stack.add( 'CREATE INDEX `$name` ON `$tableName` (${columns.join(',')});', diff --git a/packages/orm/angel_migration_runner/lib/src/postgres/table.dart b/packages/orm/angel_migration_runner/lib/src/postgres/table.dart index 83d5e977a..48e645abd 100755 --- a/packages/orm/angel_migration_runner/lib/src/postgres/table.dart +++ b/packages/orm/angel_migration_runner/lib/src/postgres/table.dart @@ -86,6 +86,8 @@ class PostgresTable extends Table { buf.write('"$name" $col'); }); + + //print(buf); } } diff --git a/packages/orm/angel_migration_runner/pubspec.yaml b/packages/orm/angel_migration_runner/pubspec.yaml index 383dac3dd..d4d1eb4a1 100755 --- a/packages/orm/angel_migration_runner/pubspec.yaml +++ b/packages/orm/angel_migration_runner/pubspec.yaml @@ -4,7 +4,7 @@ description: The implementation of database migration for Angel3 framework. Desi homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_migration_runner environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_migration: ^8.3.0 angel3_orm: ^8.2.0 diff --git a/packages/orm/angel_migration_runner/test/mariadb_test.dart b/packages/orm/angel_migration_runner/test/mariadb_test.dart index 9e6330f04..29932f254 100644 --- a/packages/orm/angel_migration_runner/test/mariadb_test.dart +++ b/packages/orm/angel_migration_runner/test/mariadb_test.dart @@ -30,6 +30,7 @@ void main() async { runner = MariaDbMigrationRunner( conn, migrations: [ + CarMigration(), UserMigration(), TodoMigration(), ItemMigration(), diff --git a/packages/orm/angel_migration_runner/test/models/mysql_todo.dart b/packages/orm/angel_migration_runner/test/models/mysql_todo.dart index 178aef4b5..7dfd96ef9 100644 --- a/packages/orm/angel_migration_runner/test/models/mysql_todo.dart +++ b/packages/orm/angel_migration_runner/test/models/mysql_todo.dart @@ -51,3 +51,33 @@ class ItemMigration extends Migration { @override void down(Schema schema) => schema.drop('items'); } + +class CarMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'cars', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'make', + length: 255, + ); + table.varChar( + 'description', + length: 255, + ); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('cars'); + } +} diff --git a/packages/orm/angel_migration_runner/test/models/pg_todo.dart b/packages/orm/angel_migration_runner/test/models/pg_todo.dart index 423049205..9316aeda5 100644 --- a/packages/orm/angel_migration_runner/test/models/pg_todo.dart +++ b/packages/orm/angel_migration_runner/test/models/pg_todo.dart @@ -51,3 +51,33 @@ class ItemMigration extends Migration { @override void down(Schema schema) => schema.drop('items'); } + +class CarMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'cars', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'make', + length: 255, + ); + table.varChar( + 'description', + length: 255, + ); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('cars'); + } +} diff --git a/packages/orm/angel_migration_runner/test/mysql_test.dart b/packages/orm/angel_migration_runner/test/mysql_test.dart index ff0623970..efe9e9ddb 100644 --- a/packages/orm/angel_migration_runner/test/mysql_test.dart +++ b/packages/orm/angel_migration_runner/test/mysql_test.dart @@ -35,6 +35,7 @@ void main() async { runner = MySqlMigrationRunner( conn, migrations: [ + CarMigration(), UserMigration(), TodoMigration(), ItemMigration(), diff --git a/packages/orm/angel_migration_runner/test/pg_test.dart b/packages/orm/angel_migration_runner/test/pg_test.dart index 8075bcf31..fe83c59b6 100644 --- a/packages/orm/angel_migration_runner/test/pg_test.dart +++ b/packages/orm/angel_migration_runner/test/pg_test.dart @@ -33,6 +33,7 @@ void main() async { runner = PostgresMigrationRunner( conn, migrations: [ + CarMigration(), UserMigration(), TodoMigration(), ItemMigration(), diff --git a/packages/orm/angel_orm/ORM_spcification.md b/packages/orm/angel_orm/ORM_spcification.md new file mode 100644 index 000000000..fdef6d7ee --- /dev/null +++ b/packages/orm/angel_orm/ORM_spcification.md @@ -0,0 +1,6 @@ +# ORM Model Definition + +## Primary Key + +* Must be integer or string type +* Must be non-nullable. Default to -1 if integer and "-1" if string. diff --git a/packages/orm/angel_orm/pubspec.yaml b/packages/orm/angel_orm/pubspec.yaml index f3f9eec5a..11300a5c4 100644 --- a/packages/orm/angel_orm/pubspec.yaml +++ b/packages/orm/angel_orm/pubspec.yaml @@ -4,7 +4,7 @@ description: Runtime support for Angel3 ORM. Includes base classes for queries. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: charcode: ^1.3.0 intl: ^0.19.0 diff --git a/packages/orm/angel_orm_generator/CHANGELOG.md b/packages/orm/angel_orm_generator/CHANGELOG.md index ae9e54b31..80d524b03 100644 --- a/packages/orm/angel_orm_generator/CHANGELOG.md +++ b/packages/orm/angel_orm_generator/CHANGELOG.md @@ -2,11 +2,12 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 +* Updated `analyzer` to 7.x * Updated dependencies to the latest release -* Take @SerializableField properties into account when generating `Migration` (#98) -* Take @SerializableField properties into account when generating `Query.parseRow` (#98) +* Fixed issue #98: Take @SerializableField properties into account when generating `Migration` +* Fixed issue #98: Take @SerializableField properties into account when generating `Query.parseRow` ## 8.3.2 diff --git a/packages/orm/angel_orm_generator/example/main.g.dart b/packages/orm/angel_orm_generator/example/main.g.dart index f05ff1631..13b6eee75 100644 --- a/packages/orm/angel_orm_generator/example/main.g.dart +++ b/packages/orm/angel_orm_generator/example/main.g.dart @@ -9,29 +9,15 @@ part of 'main.dart'; class EmployeeMigration extends Migration { @override void up(Schema schema) { - schema.create( - 'employees', - (table) { - table.serial('id').primaryKey(); - table.timeStamp('created_at'); - table.timeStamp('updated_at'); - table - .varChar( - 'unique_id', - length: 255, - ) - .unique(); - table.varChar( - 'first_name', - length: 255, - ); - table.varChar( - 'last_name', - length: 255, - ); - table.double('salary'); - }, - ); + schema.create('employees', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('unique_id', length: 255).unique(); + table.varChar('first_name', length: 255); + table.varChar('last_name', length: 255); + table.double('salary'); + }); } @override @@ -196,36 +182,43 @@ class EmployeeQueryValues extends MapQueryValues { } set id(String? value) => values['id'] = value; + DateTime? get createdAt { return (values['created_at'] as DateTime?); } set createdAt(DateTime? value) => values['created_at'] = value; + DateTime? get updatedAt { return (values['updated_at'] as DateTime?); } set updatedAt(DateTime? value) => values['updated_at'] = value; + String? get uniqueId { return (values['unique_id'] as String?); } set uniqueId(String? value) => values['unique_id'] = value; + String? get firstName { return (values['first_name'] as String?); } set firstName(String? value) => values['first_name'] = value; + String? get lastName { return (values['last_name'] as String?); } set lastName(String? value) => values['last_name'] = value; + double? get salary { return (values['salary'] as double?) ?? 0.0; } set salary(double? value) => values['salary'] = value; + void copyFrom(Employee model) { createdAt = model.createdAt; updatedAt = model.updatedAt; @@ -355,8 +348,10 @@ class EmployeeSerializer extends Codec { @override EmployeeEncoder get encoder => const EmployeeEncoder(); + @override EmployeeDecoder get decoder => const EmployeeDecoder(); + static Employee fromMap(Map map) { return Employee( id: map['id'] as String?, diff --git a/packages/orm/angel_orm_generator/lib/src/migration_generator.dart b/packages/orm/angel_orm_generator/lib/src/migration_generator.dart index 48711489f..ebe03ddfb 100644 --- a/packages/orm/angel_orm_generator/lib/src/migration_generator.dart +++ b/packages/orm/angel_orm_generator/lib/src/migration_generator.dart @@ -57,7 +57,7 @@ class MigrationGenerator extends GeneratorForAnnotation { var lib = generateMigrationLibrary(ctx, element, resolver, buildStep); //if (lib == null) return null; - return DartFormatter() + return DartFormatter(languageVersion: DartFormatter.latestLanguageVersion) .format(lib.accept(DartEmitter(useNullSafetySyntax: true)).toString()); } diff --git a/packages/orm/angel_orm_generator/lib/src/orm_generator.dart b/packages/orm/angel_orm_generator/lib/src/orm_generator.dart index 7054ed36c..8ec9e6853 100644 --- a/packages/orm/angel_orm_generator/lib/src/orm_generator.dart +++ b/packages/orm/angel_orm_generator/lib/src/orm_generator.dart @@ -249,6 +249,7 @@ class OrmGenerator extends GeneratorForAnnotation { // Build the arguments for model var args = {}; for (var field in ctx.effectiveFields) { + print("Field: $field"); var fType = field.type; Reference type = convertTypeReference(fType); if (isSpecialId(ctx, field)) { @@ -745,7 +746,7 @@ class OrmGenerator extends GeneratorForAnnotation { if (const TypeChecker.fromRuntime(int).isExactlyType(type) || const TypeChecker.fromRuntime(double).isExactlyType(type) || isSpecialId(ctx, field)) { - var typeName = type.getDisplayString(); + var typeName = type.getDisplayString().replaceAll('?', ''); if (isSpecialId(ctx, field)) { typeName = 'int'; } diff --git a/packages/orm/angel_orm_generator/lib/src/readers.dart b/packages/orm/angel_orm_generator/lib/src/readers.dart index 9964d4fdd..4ee98c622 100644 --- a/packages/orm/angel_orm_generator/lib/src/readers.dart +++ b/packages/orm/angel_orm_generator/lib/src/readers.dart @@ -61,8 +61,8 @@ class RelationshipReader { return 'fullOuterJoin'; case JoinType.self: return 'selfJoin'; - default: - return 'join'; + //default: + // return 'join'; } } diff --git a/packages/orm/angel_orm_generator/pubspec.yaml b/packages/orm/angel_orm_generator/pubspec.yaml index 6348c482a..31bf5ca48 100644 --- a/packages/orm/angel_orm_generator/pubspec.yaml +++ b/packages/orm/angel_orm_generator/pubspec.yaml @@ -4,22 +4,22 @@ description: Code generators for Angel3 ORM. Generates query builder classes. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_generator environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_model: ^8.2.0 angel3_serialize: ^8.2.0 angel3_orm: ^8.2.0 angel3_serialize_generator: ^8.3.0 - analyzer: ^6.5.0 + analyzer: ^7.2.0 inflection3: ^0.5.3+2 build: ^2.4.0 build_config: ^1.1.0 code_builder: ^4.4.0 - dart_style: ^2.3.0 + dart_style: ^3.0.0 meta: ^1.9.0 path: ^1.8.0 recase: ^4.1.0 - source_gen: ^1.3.0 + source_gen: ^2.0.0 collection: ^1.17.0 logging: ^1.2.0 optional: ^6.0.0 @@ -29,7 +29,7 @@ dev_dependencies: build_runner: ^2.4.0 test: ^1.24.0 lints: ^5.0.0 -#dependency_overrides: +dependency_overrides: # angel3_container: # path: ../../container/angel_container # angel3_framework: @@ -44,8 +44,8 @@ dev_dependencies: # path: ../../mock_request # angel3_serialize: # path: ../../serialize/angel_serialize -# angel3_serialize_generator: -# path: ../../serialize/angel_serialize_generator + angel3_serialize_generator: + path: ../../serialize/angel_serialize_generator # angel3_orm: # path: ../angel_orm # angel3_migration: diff --git a/packages/orm/angel_orm_mysql/CHANGELOG.md b/packages/orm/angel_orm_mysql/CHANGELOG.md index 957b46027..50dec8804 100644 --- a/packages/orm/angel_orm_mysql/CHANGELOG.md +++ b/packages/orm/angel_orm_mysql/CHANGELOG.md @@ -2,10 +2,12 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 -* Updated dependencies to the latest release -* Take @SerializableField properties into account when generating `Query.parseRow` (#98) +* Updated dependencies to the latest releases +* Fixed issue #98: Take `@SerializableField` properties into account when generating `Query.parseRow` +* Migrated test cases from `angel3_orm_test` into the package +* Removed dependency on `angel3_orm_test` ## 8.2.0 diff --git a/packages/orm/angel_orm_mysql/pubspec.yaml b/packages/orm/angel_orm_mysql/pubspec.yaml index d161d249d..c7b8957fc 100644 --- a/packages/orm/angel_orm_mysql/pubspec.yaml +++ b/packages/orm/angel_orm_mysql/pubspec.yaml @@ -4,7 +4,7 @@ description: MySQL support for Angel3 ORM. Includes functionality for querying a homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_mysql environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_orm: ^8.2.0 logging: ^1.2.0 @@ -12,24 +12,29 @@ dependencies: mysql_client: ^0.0.27 optional: ^6.1.0 dev_dependencies: - angel3_orm_generator: ^8.3.0 - angel3_orm_test: ^8.2.0 - build_runner: ^2.4.0 + belatuk_pretty_logging: ^6.1.0 + angel3_orm_generator: ^8.0.0 + angel3_serialize: ^8.0.0 + angel3_migration: ^8.0.0 + angel3_migration_runner: ^8.0.0 test: ^1.24.0 lints: ^5.0.0 - -# dependency_overrides: + io: ^1.0.4 + build_runner: ^2.4.0 +dependency_overrides: # angel3_serialize: # path: ../../serialize/angel_serialize -# angel3_serialize_generator: -# path: ../../serialize/angel_serialize_generator + angel3_serialize_generator: + path: ../../serialize/angel_serialize_generator # angel3_model: # path: ../../model # angel3_orm_test: # path: ../angel_orm_test # angel3_orm: # path: ../angel_orm -# angel3_orm_generator: -# path: ../angel_orm_generator -# angel3_migration: -# path: ../angel_migration + angel3_orm_generator: + path: ../angel_orm_generator +# angel3_migration: +# path: ../angel_migration + angel3_migration_runner: + path: ../angel_migration_runner diff --git a/packages/orm/angel_orm_mysql/test/all_test.dart b/packages/orm/angel_orm_mysql/test/all_test.dart deleted file mode 100644 index 14b9fcdb9..000000000 --- a/packages/orm/angel_orm_mysql/test/all_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:angel3_orm_test/angel3_orm_test.dart'; -import 'package:logging/logging.dart'; -import 'package:test/test.dart'; -import 'common.dart'; - -void main() { - Logger.root.onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - - group('mysql', () { - group( - 'belongsTo', - () => belongsToTests(createTables(['author', 'book']), - close: dropTables)); - group( - 'edgeCase', - () => edgeCaseTests( - createTables(['unorthodox', 'weird_join', 'song', 'numba']), - close: dropTables)); - group('enumAndNested', - () => enumAndNestedTests(createTables(['has_car']), close: dropTables)); - group('hasMany', - () => hasManyTests(createTables(['tree', 'fruit']), close: dropTables)); - // NOTE: MySQL/MariaDB do not support jsonb data type - //group('hasMap', () => hasMapTests(createTables(['has_maps']), close: dropTables)); - // NOTE: mysql1 driver do not support CAST(); - //group('hasOne', () => hasOneTests(createTables(['legs', 'feet']), close: dropTables)); - group( - 'manyToMany', - () => manyToManyTests(createTables(['user', 'role', 'user_role']), - close: dropTables)); - group('standalone', - () => standaloneTests(createTables(['car']), close: dropTables)); - }); -} diff --git a/packages/orm/angel_orm_mysql/test/belongs_to_test.dart b/packages/orm/angel_orm_mysql/test/belongs_to_test.dart new file mode 100644 index 000000000..c53d770a3 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/belongs_to_test.dart @@ -0,0 +1,179 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/book.dart'; + +import 'util.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Author? jkRowling; + Author? jameson; + Book? deathlyHallows; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [AuthorMigration(), BookMigration()]); + + // Insert an author + var query = AuthorQuery()..values.name = 'J.K. Rowling'; + jkRowling = (await query.insert(executor)).value; + + query.values.name = 'J.K. Jameson'; + jameson = (await query.insert(executor)).value; + + // And a book + var bookQuery = BookQuery(); + bookQuery.values + ..authorId = jkRowling?.idAsInt ?? 0 + ..partnerAuthorId = jameson?.idAsInt ?? 0 + ..name = 'Deathly Hallows'; + + deathlyHallows = (await bookQuery.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + group('selects', () { + test('select all', () async { + var query = BookQuery(); + var books = await query.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('select one', () async { + var query = BookQuery(); + query.where!.id.equals(int.parse(deathlyHallows!.id!)); + //print(query.compile({})); + + var bookOpt = await query.getOne(executor); + expect(bookOpt.isPresent, true); + bookOpt.ifPresent((book) { + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + }); + + test('where clause', () async { + var query = BookQuery() + ..where!.name.equals('Goblet of Fire') + ..orWhere((w) => w.authorId.equals(int.parse(jkRowling!.id!))); + //print(query.compile({})); + + var books = await query.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('union', () async { + var query1 = BookQuery()..where!.name.like('Deathly%'); + var query2 = BookQuery()..where!.authorId.equals(-1); + var query3 = BookQuery() + ..where!.name.isIn(['Goblet of Fire', 'Order of the Phoenix']); + query1 + ..union(query2) + ..unionAll(query3); + //print(query1.compile({})); + + var books = await query1.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('order by', () async { + var query = AuthorQuery()..orderBy(AuthorFields.name, descending: true); + var authors = await query.get(executor); + expect(authors, [jkRowling, jameson]); + }); + }); + + test('insert sets relationship', () { + expect(deathlyHallows!.author, jkRowling); + //expect(deathlyHallows.author, isNotNull); + //expect(deathlyHallows.author.name, rowling.name); + }); + + test('delete stream', () async { + //printSeparator('Delete stream test'); + var query = BookQuery()..where!.name.equals(deathlyHallows!.name!); + //print(query.compile({}, preamble: 'DELETE', withFields: false)); + var books = await query.delete(executor); + expect(books, hasLength(1)); + + var book = books.first; + expect(book.id, deathlyHallows?.id); + expect(book.author, isNotNull); + expect(book.author!.name, jkRowling!.name); + }); + + test('update book', () async { + var cloned = deathlyHallows!.copyWith(name: "Sorcerer's Stone"); + var query = BookQuery() + ..where?.id.equals(int.parse(cloned.id!)) + ..values.copyFrom(cloned); + var bookOpt = await (query.updateOne(executor)); + expect(bookOpt.isPresent, true); + bookOpt.ifPresent((book) { + //print(book.toJson()); + expect(book.name, cloned.name); + expect(book.author, isNotNull); + expect(book.author!.name, jkRowling!.name); + }); + }); + + group('joined subquery', () { + // To verify that the joined subquery is correct, + // we test both a query that return empty, and one + // that should return correctly. + test('returns empty on false subquery', () async { + printSeparator('False subquery test'); + var query = BookQuery()..author.where!.name.equals('Billie Jean'); + expect(await query.get(executor), isEmpty); + }); + + test('returns values on true subquery', () async { + printSeparator('True subquery test'); + var query = BookQuery()..author.where!.name.like('%Rowling%'); + expect(await query.get(executor), [deathlyHallows]); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/common.dart b/packages/orm/angel_orm_mysql/test/common.dart index 7801de316..0b29b424f 100644 --- a/packages/orm/angel_orm_mysql/test/common.dart +++ b/packages/orm/angel_orm_mysql/test/common.dart @@ -1,5 +1,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_migration_runner/mysql.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm_mysql/angel3_orm_mysql.dart'; import 'package:logging/logging.dart'; @@ -8,23 +11,37 @@ import 'package:mysql_client/mysql_client.dart'; List tmpTables = []; -FutureOr Function() createTables(List schemas) { - // For MySQL - return () => _connectToMySql(schemas); +// For MySQL +Future openMySqlConnection() async { + var connection = await MySQLConnection.createConnection( + databaseName: 'orm_test', + port: 3306, + host: "localhost", + userName: Platform.environment['MYSQL_USERNAME'] ?? 'test', + password: Platform.environment['MYSQL_PASSWORD'] ?? 'Test123', + secure: !('false' == Platform.environment['MYSQL_SECURE'])); + + await connection.connect(timeoutMs: 10000); - // For MariaDB - //return () => _connectToMariaDb(schemas); + return connection; } -// For MySQL -Future dropTables(QueryExecutor executor) async { - var sqlExecutor = (executor as MySqlExecutor); - for (var tableName in tmpTables.reversed) { - print('DROP TABLE $tableName'); - await sqlExecutor.rawConnection.execute('DROP TABLE $tableName;'); - } +Future createExecutor(MySQLConnection conn) async { + var logger = Logger('orm_mysql'); - return sqlExecutor.close(); + return MySqlExecutor(conn, logger: logger); +} + +Future createTables( + MySQLConnection conn, List models) async { + var runner = MySqlMigrationRunner(conn, migrations: models); + await runner.up(); + + return runner; +} + +Future dropTables(MigrationRunner runner) async { + await runner.reset(); } // For MariaDB @@ -86,7 +103,7 @@ String extractTableName(String createQuery) { // Executor for MySQL // create user 'test'@'localhost' identified by 'test123'; // GRANT ALL PRIVILEGES ON orm_test.* to 'test'@'localhost' WITH GRANT OPTION; -Future _connectToMySql(List schemas) async { +Future _connectToMySql(List models) async { var connection = await MySQLConnection.createConnection( databaseName: 'orm_test', port: 3306, @@ -101,6 +118,10 @@ Future _connectToMySql(List schemas) async { tmpTables.clear(); + var runner = MySqlMigrationRunner(connection, migrations: models); + await runner.up(); + + /* for (var s in schemas) { // MySQL driver does not support multiple sql queries var data = await File('test/migrations/$s.sql').readAsString(); @@ -117,6 +138,7 @@ Future _connectToMySql(List schemas) async { } } } + */ return MySqlExecutor(connection, logger: logger); } diff --git a/packages/orm/angel_orm_mysql/test/custom_expr_test.dart b/packages/orm/angel_orm_mysql/test/custom_expr_test.dart new file mode 100644 index 000000000..9159bbd81 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/custom_expr_test.dart @@ -0,0 +1,54 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/custom_expr.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + late Numbers numbersModel; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = + await createTables(conn, [NumbersMigration(), AlphabetMigration()]); + + var now = DateTime.now(); + var nQuery = NumbersQuery(); + nQuery.values + ..createdAt = now + ..updatedAt = now; + var numbersModelOpt = await nQuery.insert(executor); + numbersModelOpt.ifPresent((v) { + numbersModel = v; + }); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('fetches correct result', () async { + expect(numbersModel.two, 2); + }); + + test('in relation', () async { + var abcQuery = AlphabetQuery(); + abcQuery.values + ..value = 'abc' + ..numbersId = numbersModel.idAsInt + ..createdAt = numbersModel.createdAt + ..updatedAt = numbersModel.updatedAt; + var abcOpt = await (abcQuery.insert(executor)); + expect(abcOpt.isPresent, true); + abcOpt.ifPresent((abc) { + expect(abc.numbers, numbersModel); + expect(abc.numbers?.two, 2); + expect(abc.value, 'abc'); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/edge_case_test.dart b/packages/orm/angel_orm_mysql/test/edge_case_test.dart new file mode 100644 index 000000000..aefdabde5 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/edge_case_test.dart @@ -0,0 +1,137 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/unorthodox.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [ + UnorthodoxMigration(), + WeirdJoinMigration(), + SongMigration(), + NumbaMigration() + ]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('can create object with no id', () async { + var query = UnorthodoxQuery()..values.name = 'World'; + var modelOpt = await query.insert(executor); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + expect(model, Unorthodox(name: 'World')); + }); + }, skip: "Primary key cannot be null"); + + group('relations on non-model', () { + Unorthodox? unorthodox; + + setUp(() async { + //if (unorthodox == null) { + var query = UnorthodoxQuery()..values.name = 'Hey'; + + var unorthodoxOpt = await query.insert(executor); + unorthodoxOpt.ifPresent((value) { + unorthodox = value; + }); + //} + }); + + test('belongs to', () async { + var query = WeirdJoinQuery()..values.joinName = unorthodox!.name; + var modelOpt = await query.insert(executor); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + //print(model.toJson()); + expect(model.id, isNotNull); // Postgres should set this. + expect(model.unorthodox, unorthodox); + }); + }); + + group('layered', () { + WeirdJoin? weirdJoin; + Song? girlBlue; + + setUp(() async { + var wjQuery = WeirdJoinQuery()..values.joinName = unorthodox!.name; + + var weirdJoinOpt = await wjQuery.insert(executor); + //weirdJoin = (await wjQuery.insert(executor)).value; + weirdJoinOpt.ifPresent((value1) async { + weirdJoin = value1; + var gbQuery = SongQuery() + ..values.weirdJoinId = value1.id + ..values.title = 'Girl Blue'; + + var girlBlueOpt = await gbQuery.insert(executor); + girlBlueOpt.ifPresent((value2) { + girlBlue = value2; + }); + }); + }); + + test('has one', () async { + var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); + var wjOpt = await query.getOne(executor); + expect(wjOpt.isPresent, true); + wjOpt.ifPresent((wj) { + //print(wj.toJson()); + expect(wj.song, girlBlue); + }); + }); + + test('has many', () async { + var numbas = []; + + for (var i = 0; i < 15; i++) { + var query = NumbaQuery() + ..values.parent = weirdJoin!.id + ..values.i = i; + var modelObj = await query.insert(executor); + expect(modelObj.isPresent, true); + modelObj.ifPresent((model) { + numbas.add(model); + }); + } + + var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); + var wjObj = await query.getOne(executor); + expect(wjObj.isPresent, true); + wjObj.ifPresent((wj) { + //print(wj.toJson()); + expect(wj.numbas, numbas); + }); + }); + + test('many to many', () async { + var fooQuery = FooQuery()..values.bar = 'baz'; + var fooBar = + await fooQuery.insert(executor).then((foo) => foo.value.bar); + var pivotQuery = FooPivotQuery() + ..values.weirdJoinId = weirdJoin!.id + ..values.fooBar = fooBar; + await pivotQuery.insert(executor); + fooQuery = FooQuery()..where!.bar.equals('baz'); + + var fooOpt = await fooQuery.getOne(executor); + expect(fooOpt.isPresent, true); + fooOpt.ifPresent((foo) { + //print(foo.toJson()); + //print(weirdJoin!.toJson()); + expect(foo.weirdJoins[0].id, weirdJoin!.id); + }); + }, skip: "Primary key cannot be null"); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/enum_and_nested_test.dart b/packages/orm/angel_orm_mysql/test/enum_and_nested_test.dart new file mode 100644 index 000000000..eb795f8c6 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/enum_and_nested_test.dart @@ -0,0 +1,55 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/has_car.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [HasCarMigration()]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('insert', () async { + var query = HasCarQuery() + ..values.type = CarType.sedan + ..values.color = Color.red; + var resultOpt = await (query.insert(executor)); + expect(resultOpt.isPresent, true); + resultOpt.ifPresent((result) { + expect(result.type, CarType.sedan); + expect(result.color, Color.red); + }); + }); + + group('query', () { + HasCar? initialValue; + + setUp(() async { + var query = HasCarQuery(); + query.values.type = CarType.sedan; + initialValue = (await query.insert(executor)).value; + }); + + test('query by enum', () async { + // Check for mismatched type + var query = HasCarQuery()..where!.type.equals(CarType.atv); + var result = await query.get(executor); + expect(result, isEmpty); + + query = HasCarQuery()..where!.type.equals(initialValue!.type); + var oneResult = await query.getOne(executor); + expect(oneResult.value, initialValue); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/enum_test.dart b/packages/orm/angel_orm_mysql/test/enum_test.dart new file mode 100644 index 000000000..185e75293 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/enum_test.dart @@ -0,0 +1,12 @@ +import 'package:test/test.dart'; + +import 'models/has_car.dart'; + +void main() async { + /// See https://github.com/dart-backend/angel/pull/98 + test('enum field with custom deserializer should be parsed consistently', () { + final query = HasCarQuery(); + final hasCar = query.parseRow([null, null, null, 'R', null]).value; + expect(hasCar.color, equals(Color.red)); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/has_many_test.dart b/packages/orm/angel_orm_mysql/test/has_many_test.dart new file mode 100644 index 000000000..53fd88439 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/has_many_test.dart @@ -0,0 +1,101 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/tree.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Tree? appleTree; + late int treeId; + + setUp(() async { + var query = TreeQuery()..values.rings = 10; + + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [TreeMigration(), FruitMigration()]); + appleTree = (await query.insert(executor)).value; + treeId = int.parse(appleTree!.id!); + }); + + tearDown(() async => await dropTables(runner)); + + test('list is empty if there is nothing', () { + expect(appleTree!.rings, 10); + expect(appleTree!.fruits, isEmpty); + }); + + group('mutations', () { + Fruit? apple, banana; + + void verify(Tree tree) { + //print(tree.fruits!.map(FruitSerializer.toMap).toList()); + expect(tree.fruits, hasLength(2)); + expect(tree.fruits[0].commonName, apple!.commonName); + expect(tree.fruits[1].commonName, banana!.commonName); + } + + setUp(() async { + var appleQuery = FruitQuery() + ..values.treeId = treeId + ..values.commonName = 'Apple'; + + var bananaQuery = FruitQuery() + ..values.treeId = treeId + ..values.commonName = 'Banana'; + var appleOpt = await appleQuery.insert(executor); + var bananaOpt = await bananaQuery.insert(executor); + appleOpt.ifPresent((a) { + apple = a; + }); + bananaOpt.ifPresent((a) { + banana = a; + }); + }); + + test('can fetch any children', () async { + var query = TreeQuery()..where!.id.equals(treeId); + var treeOpt = await (query.getOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + }); + }); + + test('sets on update', () async { + var tq = TreeQuery() + ..where!.id.equals(treeId) + ..values.rings = 24; + var treeOpt = await (tq.updateOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + expect(tree.rings, 24); + }); + }); + + test('sets on delete', () async { + var tq = TreeQuery()..where!.id.equals(treeId); + var treeOpt = await (tq.deleteOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + }); + }); + + test('returns empty on false subquery', () async { + var tq = TreeQuery() + ..where!.id.equals(treeId) + ..fruits.where!.commonName.equals('Kiwi'); + var treeOpt = await (tq.getOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + expect(tree.fruits, isEmpty); + }); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/has_map_test.dart b/packages/orm/angel_orm_mysql/test/has_map_test.dart new file mode 100644 index 000000000..020c94027 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/has_map_test.dart @@ -0,0 +1,121 @@ +@Skip("Jsonb data type is not supported") + +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/has_map.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [HasMapMigration()]); + }); + + tearDown(() async => await dropTables(runner)); + + test('insert', () async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + var modelOpt = await (query.insert(executor)); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + //print(model.toString()); + + var data = HasMap(value: {'foo': 'bar'}, list: ['1', 2, 3.0]); + //print(data.toString()); + + expect(model, data); + }); + }); + + test('update', () async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + var modelOpt = await (query.insert(executor)); + expect(modelOpt.isPresent, true); + if (modelOpt.isPresent) { + var model = modelOpt.value; + //print(model.toJson()); + query = HasMapQuery()..values.copyFrom(model); + var result = await query.updateOne(executor); + expect(result.isPresent, true); + result.ifPresent((m) { + expect(m, model); + }); + } + }); + + group('query', () { + HasMap? initialValue; + + setUp(() async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + initialValue = (await query.insert(executor)).value; + }); + + /* + test('get all', () async { + var query = HasMapQuery(); + expect(await query.get(executor), [initialValue]); + }); + + test('map equals', () async { + var query = HasMapQuery(); + query.where!.value.equals({'foo': 'bar'}); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery(); + query.where?.value.equals({'foo': 'baz'}); + expect(await query.get(executor), isEmpty); + }); + */ + + test('list equals', () async { + var query = HasMapQuery(); + + query.where?.list.equals(['1', 2, 3.0]); + + //print(query.substitutionValues); + + var result = await query.get(executor); + expect(result, [initialValue]); + + query = HasMapQuery(); + query.where?.list.equals(['10', 20, 30.0]); + var result2 = await query.get(executor); + expect(result2, isEmpty); + }); + + /* + test('property equals', () async { + var query = HasMapQuery()..where?.value['foo'].asString?.equals('bar'); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery()..where?.value['foo'].asString?.equals('baz'); + expect(await query.get(executor), []); + }); + + test('index equals', () async { + var query = HasMapQuery()..where?.list[0].asString?.equals('1'); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery()..where?.list[1].asInt?.equals(3); + expect(await query.get(executor), []); + }); + */ + }); +} diff --git a/packages/orm/angel_orm_mysql/test/has_one_test.dart b/packages/orm/angel_orm_mysql/test/has_one_test.dart new file mode 100644 index 000000000..15d57da2f --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/has_one_test.dart @@ -0,0 +1,129 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/leg.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Leg? originalLeg; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [LegMigration(), FootMigration()]); + var query = LegQuery()..values.name = 'Left'; + originalLeg = (await query.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + test('sets to null if no child', () async { + //print(LegQuery().compile({})); + var query = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var legOpt = await (query.getOne(executor)); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, originalLeg?.name); + expect(leg.id, originalLeg?.id); + expect(leg.foot, isNull); + }); + }); + + test('can fetch one foot', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.getOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.name, originalLeg!.name); + expect(leg.id, originalLeg!.id); + footOpt.ifPresent((foot) { + expect(leg.foot, isNotNull); + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('only fetches one foot even if there are multiple', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 24; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.getOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.name, originalLeg!.name); + expect(leg.id, originalLeg!.id); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets foot on update', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery() + ..where!.id.equals(int.parse(originalLeg!.id!)) + ..values.copyFrom(originalLeg!.copyWith(name: 'Right')); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.updateOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, 'Right'); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets foot on delete', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.deleteOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, originalLeg?.name); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets null on false subquery', () async { + var legQuery = LegQuery() + ..where!.id.equals(originalLeg!.idAsInt) + ..foot.where!.legId.equals(originalLeg!.idAsInt + 1024); + var legOpt = await (legQuery.getOne(executor)); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.foot, isNull); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/join_test.dart b/packages/orm/angel_orm_mysql/test/join_test.dart new file mode 100644 index 000000000..713d4744e --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/join_test.dart @@ -0,0 +1,95 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; + +import 'common.dart'; +import 'models/person.dart'; +import 'models/person_order.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + Person? originalPerson; + PersonOrder? originalOrder1; + PersonOrder? originalOrder2; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = + await createTables(conn, [PersonMigration(), PersonOrderMigration()]); + var query = PersonQuery() + ..values.name = 'DebuggerX' + ..values.age = 29; + originalPerson = (await query.insert(executor)).value; + + var orderQuery = PersonOrderQuery() + ..values.personId = originalPerson!.idAsInt + ..values.name = 'Order1' + ..values.price = 128 + ..values.deleted = false; + + originalOrder1 = (await orderQuery.insert(executor)).value; + + orderQuery = PersonOrderQuery() + ..values.personId = originalPerson!.idAsInt + ..values.name = 'Order2' + ..values.price = 256 + ..values.deleted = true; + + originalOrder2 = (await orderQuery.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + test('select person with last order info', () async { + var orderQuery = PersonOrderQuery(); + var query = PersonWithLastOrderQuery(); + query.join( + orderQuery.tableName, PersonFields.id, PersonOrderFields.personId, + alias: 'po'); + query.where?.name.equals(originalPerson!.name!); + query.orderBy('po.id', descending: true); + var personWithOrderInfo = await query.getOne(executor); + expect(personWithOrderInfo.value.lastOrderName, originalOrder2?.name); + }); + + test('select person with last valid order info', () async { + var orderQuery = PersonOrderQuery(); + var query = PersonWithLastOrderQuery(); + query.join( + orderQuery.tableName, PersonFields.id, PersonOrderFields.personId, + alias: 'po'); + query.where?.name.equals(originalPerson!.name!); + query.orderBy('po.id', descending: true); + query.where?.raw('po.deleted = false'); + var personWithOrderInfo = await query.getOne(executor); + expect(personWithOrderInfo.value.lastOrderName, originalOrder1?.name); + }); + + test('select orders with person info', () async { + var personQuery = PersonQuery(); + var query = OrderWithPersonInfoQuery(); + query.join( + personQuery.tableName, PersonOrderFields.personId, PersonFields.id, + alias: 'P'); + query.where?.raw("P.name = '${originalPerson?.name}'"); + var orders = await query.get(executor); + expect( + orders.every((element) => + element.personName == originalPerson?.name && + element.personAge == originalPerson?.age), + true); + }); + + test('select orders with multi order by fields', () async { + var query = PersonOrderQuery(); + query.orderBy(PersonOrderFields.id, descending: true); + query.orderBy(PersonOrderFields.personId, descending: true); + var orders = await query.get(executor); + expect(orders.first.idAsInt > orders.last.idAsInt, true); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/many_to_many_test.dart b/packages/orm/angel_orm_mysql/test/many_to_many_test.dart new file mode 100644 index 000000000..8cbcca57d --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/many_to_many_test.dart @@ -0,0 +1,143 @@ +import 'dart:async'; +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/user.dart'; +import 'util.dart'; + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + Role? canPub, canSub; + User? thosakwe; + + /* + Future dumpQuery(String query) async { + if (Platform.environment.containsKey('STFU')) return; + print('\n'); + print('=================================================='); + print(' DUMPING QUERY'); + print(query); + //var rows = await executor.query(null, query, {}); + var rows = await executor.query('', query, {}); + print('\n${rows.length} row(s):'); + for (var r in rows) { + print(' * $r'); + } + print('==================================================\n\n'); + } + */ + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables( + conn, [UserMigration(), RoleMigration(), RoleUserMigration()]); + + var canPubQuery = RoleQuery()..values.name = 'can_pub'; + var canSubQuery = RoleQuery()..values.name = 'can_sub'; + canPub = (await canPubQuery.insert(executor)).value; + print('=== CANPUB: ${canPub?.toJson()}'); + // await dumpQuery(canPubQuery.compile(Set())); + canSub = (await canSubQuery.insert(executor)).value; + print('=== CANSUB: ${canSub?.toJson()}'); + + var thosakweQuery = UserQuery(); + thosakweQuery.values + ..username = 'thosakwe' + ..password = 'Hahahahayoureallythoughtiwasstupidenoughtotypethishere' + ..email = 'thosakwe AT gmail.com'; + var result = await thosakweQuery.insert(executor); + thosakwe = result.value; + print('=== THOSAKWE: ${thosakwe?.toJson()}'); + + // Allow thosakwe to publish... + printSeparator('Allow thosakwe to publish'); + var thosakwePubQuery = RoleUserQuery(); + thosakwePubQuery.values + ..userId = int.parse(thosakwe!.id!) + ..roleId = int.parse(canPub!.id!); + await thosakwePubQuery.insert(executor); + + // Allow thosakwe to subscribe... + printSeparator('Allow thosakwe to subscribe'); + var thosakweSubQuery = RoleUserQuery(); + thosakweSubQuery.values + ..userId = int.parse(thosakwe!.id!) + ..roleId = int.parse(canSub!.id!); + await thosakweSubQuery.insert(executor); + + // Print all users... + // await dumpQuery('select * from users;'); + // await dumpQuery('select * from roles;'); + // await dumpQuery('select * from role_users;'); + // var query = RoleQuery()..where.id.equals(canPub.idAsInt); + // await dumpQuery(query.compile(Set())); + + print('\n'); + print('=================================================='); + print(' GOOD STUFF BEGINS HERE '); + print('==================================================\n\n'); + }); + + tearDown(() async => await dropTables(runner)); + + Future fetchThosakwe() async { + var query = UserQuery()..where!.id.equals(int.parse(thosakwe!.id!)); + var userOpt = await query.getOne(executor); + expect(userOpt.isPresent, true); + if (userOpt.isPresent) { + return userOpt.value; + } else { + return null; + } + } + + test('fetch roles for user', () async { + printSeparator('Fetch roles for user test'); + var user = await fetchThosakwe(); + + expect(user?.roles, hasLength(2)); + expect(user?.roles, contains(canPub)); + expect(user?.roles, contains(canSub)); + }); + + test('fetch users for role', () async { + for (var role in [canPub, canSub]) { + var query = RoleQuery()..where!.id.equals(role!.idAsInt); + var rOpt = await query.getOne(executor); + expect(rOpt.isPresent, true); + rOpt.ifPresent((r) async { + expect(r.users.toList(), [thosakwe]); + }); + } + }); + + test('only fetches linked', () async { + // Create a new user. The roles list should be empty, + // be there are no related rules. + var userQuery = UserQuery(); + userQuery.values + ..username = 'Prince' + ..password = 'Rogers' + ..email = 'Nelson'; + var userOpt = await userQuery.insert(executor); + expect(userOpt.isPresent, true); + if (userOpt.isPresent) { + var user = userOpt.value; + expect(user.roles, isEmpty); + + // Fetch again, just to be doubly sure. + var query = UserQuery()..where!.id.equals(user.idAsInt); + var fetchedOpt = await query.getOne(executor); + expect(fetchedOpt.isPresent, true); + fetchedOpt.ifPresent((fetched) { + expect(fetched.roles, isEmpty); + }); + } + }); +} diff --git a/packages/orm/angel_orm_mysql/test/migrations/car.sql b/packages/orm/angel_orm_mysql/test/migrations/car.sql index cb4b62ef2..f6c78f576 100644 --- a/packages/orm/angel_orm_mysql/test/migrations/car.sql +++ b/packages/orm/angel_orm_mysql/test/migrations/car.sql @@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS cars ( family_friendly BOOLEAN NOT NULL, recalled_at datetime, created_at datetime, - updated_at datetime + updated_at datetime, + price double ); \ No newline at end of file diff --git a/packages/orm/angel_orm_mysql/test/models/asset.dart b/packages/orm/angel_orm_mysql/test/models/asset.dart new file mode 100644 index 000000000..2a3bc4961 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/asset.dart @@ -0,0 +1,26 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'asset.g.dart'; + +@serializable +@orm +abstract class _Item extends Model { + String get description; +} + +@serializable +@orm +abstract class _Asset extends Model { + String get description; + + String get name; + + @Column(type: ColumnType.numeric, precision: 17, scale: 3) + double get price; + + @hasMany + List<_Item> get items; +} diff --git a/packages/orm/angel_orm_mysql/test/models/asset.g.dart b/packages/orm/angel_orm_mysql/test/models/asset.g.dart new file mode 100644 index 000000000..14d8dece6 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/asset.g.dart @@ -0,0 +1,805 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class ItemMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'items', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'description', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('items'); + } +} + +class AssetMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'assets', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'description', + length: 255, + ); + table.varChar( + 'name', + length: 255, + ); + table.double('price'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'assets', + cascade: true, + ); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class ItemQuery extends Query { + ItemQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = ItemQueryWhere(this); + } + + @override + final ItemQueryValues values = ItemQueryValues(); + + List _selectedFields = []; + + ItemQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'items'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'description', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + ItemQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + ItemQueryWhere? get where { + return _where; + } + + @override + ItemQueryWhere newWhereClause() { + return ItemQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Item( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + description: fields.contains('description') ? (row[3] as String) : '', + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class ItemQueryWhere extends QueryWhere { + ItemQueryWhere(ItemQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder description; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + description, + ]; + } +} + +class ItemQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + void copyFrom(Item model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + description = model.description; + } +} + +class AssetQuery extends Query { + AssetQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AssetQueryWhere(this); + leftJoin( + _items = ItemQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'asset_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'description', + ], + trampoline: trampoline, + ); + } + + @override + final AssetQueryValues values = AssetQueryValues(); + + List _selectedFields = []; + + AssetQueryWhere? _where; + + late ItemQuery _items; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'assets'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'description', + 'name', + 'price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AssetQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AssetQueryWhere? get where { + return _where; + } + + @override + AssetQueryWhere newWhereClause() { + return AssetQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Asset( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + description: fields.contains('description') ? (row[3] as String) : '', + name: fields.contains('name') ? (row[4] as String) : '', + price: fields.contains('price') ? mapToDouble(row[5]) : 0.0, + ); + if (row.length > 6) { + var modelOpt = ItemQuery().parseRow(row.skip(6).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(items: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + ItemQuery get items { + return _items; + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } +} + +class AssetQueryWhere extends QueryWhere { + AssetQueryWhere(AssetQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder description; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + description, + name, + price, + ]; + } +} + +class AssetQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + String get name { + return (values['name'] as String); + } + + set name(String value) => values['name'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + void copyFrom(Asset model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + description = model.description; + name = model.name; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Item extends _Item { + Item({ + this.id, + this.createdAt, + this.updatedAt, + required this.description, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String description; + + Item copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + }) { + return Item( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + description: description ?? this.description); + } + + @override + bool operator ==(other) { + return other is _Item && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.description == description; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + description, + ]); + } + + @override + String toString() { + return 'Item(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, description=$description)'; + } + + Map toJson() { + return ItemSerializer.toMap(this); + } +} + +@generatedSerializable +class Asset extends _Asset { + Asset({ + this.id, + this.createdAt, + this.updatedAt, + required this.description, + required this.name, + required this.price, + List<_Item> items = const [], + }) : items = List.unmodifiable(items); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String description; + + @override + String name; + + @override + double price; + + @override + List<_Item> items; + + Asset copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + String? name, + double? price, + List<_Item>? items, + }) { + return Asset( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + description: description ?? this.description, + name: name ?? this.name, + price: price ?? this.price, + items: items ?? this.items); + } + + @override + bool operator ==(other) { + return other is _Asset && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.description == description && + other.name == name && + other.price == price && + ListEquality<_Item>(DefaultEquality<_Item>()) + .equals(other.items, items); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + description, + name, + price, + items, + ]); + } + + @override + String toString() { + return 'Asset(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, description=$description, name=$name, price=$price, items=$items)'; + } + + Map toJson() { + return AssetSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const ItemSerializer itemSerializer = ItemSerializer(); + +class ItemEncoder extends Converter { + const ItemEncoder(); + + @override + Map convert(Item model) => ItemSerializer.toMap(model); +} + +class ItemDecoder extends Converter { + const ItemDecoder(); + + @override + Item convert(Map map) => ItemSerializer.fromMap(map); +} + +class ItemSerializer extends Codec { + const ItemSerializer(); + + @override + ItemEncoder get encoder => const ItemEncoder(); + + @override + ItemDecoder get decoder => const ItemDecoder(); + + static Item fromMap(Map map) { + return Item( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + description: map['description'] as String); + } + + static Map toMap(_Item? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'description': model.description + }; + } +} + +abstract class ItemFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + description, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String description = 'description'; +} + +const AssetSerializer assetSerializer = AssetSerializer(); + +class AssetEncoder extends Converter { + const AssetEncoder(); + + @override + Map convert(Asset model) => AssetSerializer.toMap(model); +} + +class AssetDecoder extends Converter { + const AssetDecoder(); + + @override + Asset convert(Map map) => AssetSerializer.fromMap(map); +} + +class AssetSerializer extends Codec { + const AssetSerializer(); + + @override + AssetEncoder get encoder => const AssetEncoder(); + + @override + AssetDecoder get decoder => const AssetDecoder(); + + static Asset fromMap(Map map) { + return Asset( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + description: map['description'] as String, + name: map['name'] as String, + price: map['price'] as double, + items: map['items'] is Iterable + ? List.unmodifiable(((map['items'] as Iterable).whereType()) + .map(ItemSerializer.fromMap)) + : []); + } + + static Map toMap(_Asset? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'description': model.description, + 'name': model.name, + 'price': model.price, + 'items': model.items.map((m) => ItemSerializer.toMap(m)).toList() + }; + } +} + +abstract class AssetFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + description, + name, + price, + items, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String description = 'description'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String items = 'items'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/bike.dart b/packages/orm/angel_orm_mysql/test/models/bike.dart new file mode 100644 index 000000000..08db8929b --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/bike.dart @@ -0,0 +1,22 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'bike.g.dart'; + +@serializable +@orm +abstract class _Bike extends Model { + String get make; + + String get description; + + bool get familyFriendly; + + DateTime get recalledAt; + + double get price; + + int get width; +} diff --git a/packages/orm/angel_orm_mysql/test/models/bike.g.dart b/packages/orm/angel_orm_mysql/test/models/bike.g.dart new file mode 100644 index 000000000..687a0b19b --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/bike.g.dart @@ -0,0 +1,490 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bike.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BikeMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'bikes', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'make', + length: 255, + ); + table.varChar( + 'description', + length: 255, + ); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + table.integer('width'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('bikes'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BikeQuery extends Query { + BikeQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BikeQueryWhere(this); + } + + @override + final BikeQueryValues values = BikeQueryValues(); + + List _selectedFields = []; + + BikeQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'bikes'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + 'width', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BikeQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BikeQueryWhere? get where { + return _where; + } + + @override + BikeQueryWhere newWhereClause() { + return BikeQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Bike( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String) : '', + description: fields.contains('description') ? (row[4] as String) : '', + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : false, + recalledAt: fields.contains('recalled_at') + ? mapToDateTime(row[6]) + : DateTime.parse("1970-01-01 00:00:00"), + price: fields.contains('price') ? mapToDouble(row[7]) : 0.0, + width: fields.contains('width') ? (row[8] as int) : 0, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class BikeQueryWhere extends QueryWhere { + BikeQueryWhere(BikeQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + width = NumericSqlExpressionBuilder( + query, + 'width', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + final NumericSqlExpressionBuilder width; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + } +} + +class BikeQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get make { + return (values['make'] as String); + } + + set make(String value) => values['make'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + bool get familyFriendly { + return (values['family_friendly'] as bool); + } + + set familyFriendly(bool value) => values['family_friendly'] = value; + + DateTime get recalledAt { + return (values['recalled_at'] as DateTime); + } + + set recalledAt(DateTime value) => values['recalled_at'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + int get width { + return (values['width'] as int); + } + + set width(int value) => values['width'] = value; + + void copyFrom(Bike model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + width = model.width; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Bike extends _Bike { + Bike({ + this.id, + this.createdAt, + this.updatedAt, + required this.make, + required this.description, + required this.familyFriendly, + required this.recalledAt, + required this.price, + required this.width, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String make; + + @override + String description; + + @override + bool familyFriendly; + + @override + DateTime recalledAt; + + @override + double price; + + @override + int width; + + Bike copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + int? width, + }) { + return Bike( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price, + width: width ?? this.width); + } + + @override + bool operator ==(other) { + return other is _Bike && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price && + other.width == width; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]); + } + + @override + String toString() { + return 'Bike(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price, width=$width)'; + } + + Map toJson() { + return BikeSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BikeSerializer bikeSerializer = BikeSerializer(); + +class BikeEncoder extends Converter { + const BikeEncoder(); + + @override + Map convert(Bike model) => BikeSerializer.toMap(model); +} + +class BikeDecoder extends Converter { + const BikeDecoder(); + + @override + Bike convert(Map map) => BikeSerializer.fromMap(map); +} + +class BikeSerializer extends Codec { + const BikeSerializer(); + + @override + BikeEncoder get encoder => const BikeEncoder(); + + @override + BikeDecoder get decoder => const BikeDecoder(); + + static Bike fromMap(Map map) { + return Bike( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String, + description: map['description'] as String, + familyFriendly: map['family_friendly'] as bool, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : DateTime.parse("1970-01-01 00:00:00"), + price: map['price'] as double, + width: map['width'] as int); + } + + static Map toMap(_Bike? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt.toIso8601String(), + 'price': model.price, + 'width': model.width + }; + } +} + +abstract class BikeFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; + + static const String width = 'width'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/boat.d.ts b/packages/orm/angel_orm_mysql/test/models/boat.d.ts new file mode 100644 index 000000000..e7706ea5a --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/boat.d.ts @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +declare module 'angel3_orm_postgres' { + interface Boat { + id?: string; + created_at?: any; + updated_at?: any; + make?: string; + description?: string; + family_friendly?: boolean; + recalled_at?: any; + price?: number; + width?: number; + } +} \ No newline at end of file diff --git a/packages/orm/angel_orm_mysql/test/models/boat.dart b/packages/orm/angel_orm_mysql/test/models/boat.dart new file mode 100644 index 000000000..b1ccf47c8 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/boat.dart @@ -0,0 +1,28 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'boat.g.dart'; + +@Serializable(serializers: Serializers.all) +@orm +abstract class _Boat extends Model { + @Column(defaultValue: '') + String get make; + + @Column(defaultValue: 'none') + String get description; + + @Column(defaultValue: false) + bool get familyFriendly; + + //@SerializableField(defaultValue: '1970-01-01 00:00:01') + DateTime get recalledAt; + + @Column(defaultValue: 0.0) + double get price; + + @Column(defaultValue: 0) + int get width; +} diff --git a/packages/orm/angel_orm_mysql/test/models/boat.g.dart b/packages/orm/angel_orm_mysql/test/models/boat.g.dart new file mode 100644 index 000000000..73603903c --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/boat.g.dart @@ -0,0 +1,494 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'boat.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BoatMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'boats', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table + .varChar( + 'make', + length: 255, + ) + .defaultsTo(''); + table + .varChar( + 'description', + length: 255, + ) + .defaultsTo('none'); + table.boolean('family_friendly').defaultsTo(false); + table.timeStamp('recalled_at'); + table.double('price').defaultsTo(0.0); + table.integer('width').defaultsTo(0); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('boats'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BoatQuery extends Query { + BoatQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BoatQueryWhere(this); + } + + @override + final BoatQueryValues values = BoatQueryValues(); + + List _selectedFields = []; + + BoatQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'boats'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + 'width', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BoatQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BoatQueryWhere? get where { + return _where; + } + + @override + BoatQueryWhere newWhereClause() { + return BoatQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Boat( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String) : '', + description: fields.contains('description') ? (row[4] as String) : '', + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : false, + recalledAt: fields.contains('recalled_at') + ? mapToDateTime(row[6]) + : DateTime.parse("1970-01-01 00:00:00"), + price: fields.contains('price') ? mapToDouble(row[7]) : 0.0, + width: fields.contains('width') ? (row[8] as int) : 0, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class BoatQueryWhere extends QueryWhere { + BoatQueryWhere(BoatQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + width = NumericSqlExpressionBuilder( + query, + 'width', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + final NumericSqlExpressionBuilder width; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + } +} + +class BoatQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get make { + return (values['make'] as String); + } + + set make(String value) => values['make'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + bool get familyFriendly { + return (values['family_friendly'] as bool); + } + + set familyFriendly(bool value) => values['family_friendly'] = value; + + DateTime get recalledAt { + return (values['recalled_at'] as DateTime); + } + + set recalledAt(DateTime value) => values['recalled_at'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + int get width { + return (values['width'] as int); + } + + set width(int value) => values['width'] = value; + + void copyFrom(Boat model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + width = model.width; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Boat extends _Boat { + Boat({ + this.id, + this.createdAt, + this.updatedAt, + required this.make, + required this.description, + required this.familyFriendly, + required this.recalledAt, + required this.price, + required this.width, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String make; + + @override + String description; + + @override + bool familyFriendly; + + @override + DateTime recalledAt; + + @override + double price; + + @override + int width; + + Boat copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + int? width, + }) { + return Boat( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price, + width: width ?? this.width); + } + + @override + bool operator ==(other) { + return other is _Boat && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price && + other.width == width; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]); + } + + @override + String toString() { + return 'Boat(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price, width=$width)'; + } + + Map toJson() { + return BoatSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BoatSerializer boatSerializer = BoatSerializer(); + +class BoatEncoder extends Converter { + const BoatEncoder(); + + @override + Map convert(Boat model) => BoatSerializer.toMap(model); +} + +class BoatDecoder extends Converter { + const BoatDecoder(); + + @override + Boat convert(Map map) => BoatSerializer.fromMap(map); +} + +class BoatSerializer extends Codec { + const BoatSerializer(); + + @override + BoatEncoder get encoder => const BoatEncoder(); + + @override + BoatDecoder get decoder => const BoatDecoder(); + + static Boat fromMap(Map map) { + return Boat( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String, + description: map['description'] as String, + familyFriendly: map['family_friendly'] as bool, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : DateTime.parse("1970-01-01 00:00:00"), + price: map['price'] as double, + width: map['width'] as int); + } + + static Map toMap(_Boat? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt.toIso8601String(), + 'price': model.price, + 'width': model.width + }; + } +} + +abstract class BoatFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; + + static const String width = 'width'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/book.dart b/packages/orm/angel_orm_mysql/test/models/book.dart new file mode 100644 index 000000000..3bc7f79ab --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/book.dart @@ -0,0 +1,31 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'book.g.dart'; + +@serializable +@orm +class _Book extends Model { + @BelongsTo(joinType: JoinType.inner) + _Author? author; + + @BelongsTo(localKey: 'partner_author_id', joinType: JoinType.inner) + _Author? partnerAuthor; + + String? name; +} + +@serializable +@orm +abstract class _Author extends Model { + @Column(length: 255, indexType: IndexType.unique) + @SerializableField(defaultValue: 'Tobe Osakwe') + String? get name; + + //@Column(name: "pub") + //String? publisher; +} diff --git a/packages/orm/angel_orm_mysql/test/models/book.g.dart b/packages/orm/angel_orm_mysql/test/models/book.g.dart new file mode 100644 index 000000000..e3dc720f7 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/book.g.dart @@ -0,0 +1,779 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'book.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BookMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'books', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'name', + length: 255, + ); + table + .declare( + 'author_id', + ColumnType('int'), + ) + .references( + 'authors', + 'id', + ); + table + .declare( + 'partner_author_id', + ColumnType('int'), + ) + .references( + 'authors', + 'id', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('books'); + } +} + +class AuthorMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'authors', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'name', + length: 255, + ) + ..defaultsTo('Tobe Osakwe') + ..unique(); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('authors'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BookQuery extends Query { + BookQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BookQueryWhere(this); + join( + _author = AuthorQuery( + trampoline: trampoline, + parent: this, + ), + 'author_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + join( + _partnerAuthor = AuthorQuery( + trampoline: trampoline, + parent: this, + ), + 'partner_author_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + } + + @override + final BookQueryValues values = BookQueryValues(); + + List _selectedFields = []; + + BookQueryWhere? _where; + + late AuthorQuery _author; + + late AuthorQuery _partnerAuthor; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'books'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'author_id', + 'partner_author_id', + 'name', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BookQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BookQueryWhere? get where { + return _where; + } + + @override + BookQueryWhere newWhereClause() { + return BookQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Book( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[5] as String?) : null, + ); + if (row.length > 6) { + var modelOpt = AuthorQuery().parseRow(row.skip(6).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(author: m); + }); + } + if (row.length > 10) { + var modelOpt = AuthorQuery().parseRow(row.skip(10).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(partnerAuthor: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + AuthorQuery get author { + return _author; + } + + AuthorQuery get partnerAuthor { + return _partnerAuthor; + } +} + +class BookQueryWhere extends QueryWhere { + BookQueryWhere(BookQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + authorId = NumericSqlExpressionBuilder( + query, + 'author_id', + ), + partnerAuthorId = NumericSqlExpressionBuilder( + query, + 'partner_author_id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder authorId; + + final NumericSqlExpressionBuilder partnerAuthorId; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + authorId, + partnerAuthorId, + name, + ]; + } +} + +class BookQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int get authorId { + return (values['author_id'] as int); + } + + set authorId(int value) => values['author_id'] = value; + + int get partnerAuthorId { + return (values['partner_author_id'] as int); + } + + set partnerAuthorId(int value) => values['partner_author_id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Book model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + if (model.author != null) { + values['author_id'] = model.author?.id; + } + if (model.partnerAuthor != null) { + values['partner_author_id'] = model.partnerAuthor?.id; + } + } +} + +class AuthorQuery extends Query { + AuthorQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AuthorQueryWhere(this); + } + + @override + final AuthorQueryValues values = AuthorQueryValues(); + + List _selectedFields = []; + + AuthorQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'authors'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AuthorQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AuthorQueryWhere? get where { + return _where; + } + + @override + AuthorQueryWhere newWhereClause() { + return AuthorQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Author( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class AuthorQueryWhere extends QueryWhere { + AuthorQueryWhere(AuthorQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + ]; + } +} + +class AuthorQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Author model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Book extends _Book { + Book({ + this.id, + this.createdAt, + this.updatedAt, + this.author, + this.partnerAuthor, + this.name, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Author? author; + + @override + _Author? partnerAuthor; + + @override + String? name; + + Book copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Author? author, + _Author? partnerAuthor, + String? name, + }) { + return Book( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + author: author ?? this.author, + partnerAuthor: partnerAuthor ?? this.partnerAuthor, + name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Book && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.author == author && + other.partnerAuthor == partnerAuthor && + other.name == name; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + author, + partnerAuthor, + name, + ]); + } + + @override + String toString() { + return 'Book(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, author=$author, partnerAuthor=$partnerAuthor, name=$name)'; + } + + Map toJson() { + return BookSerializer.toMap(this); + } +} + +@generatedSerializable +class Author extends _Author { + Author({ + this.id, + this.createdAt, + this.updatedAt, + this.name = 'Tobe Osakwe', + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + Author copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + }) { + return Author( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Author && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + ]); + } + + @override + String toString() { + return 'Author(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name)'; + } + + Map toJson() { + return AuthorSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BookSerializer bookSerializer = BookSerializer(); + +class BookEncoder extends Converter { + const BookEncoder(); + + @override + Map convert(Book model) => BookSerializer.toMap(model); +} + +class BookDecoder extends Converter { + const BookDecoder(); + + @override + Book convert(Map map) => BookSerializer.fromMap(map); +} + +class BookSerializer extends Codec { + const BookSerializer(); + + @override + BookEncoder get encoder => const BookEncoder(); + + @override + BookDecoder get decoder => const BookDecoder(); + + static Book fromMap(Map map) { + return Book( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + author: map['author'] != null + ? AuthorSerializer.fromMap(map['author'] as Map) + : null, + partnerAuthor: map['partner_author'] != null + ? AuthorSerializer.fromMap(map['partner_author'] as Map) + : null, + name: map['name'] as String?); + } + + static Map toMap(_Book? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'author': AuthorSerializer.toMap(model.author), + 'partner_author': AuthorSerializer.toMap(model.partnerAuthor), + 'name': model.name + }; + } +} + +abstract class BookFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + author, + partnerAuthor, + name, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String author = 'author'; + + static const String partnerAuthor = 'partner_author'; + + static const String name = 'name'; +} + +const AuthorSerializer authorSerializer = AuthorSerializer(); + +class AuthorEncoder extends Converter { + const AuthorEncoder(); + + @override + Map convert(Author model) => AuthorSerializer.toMap(model); +} + +class AuthorDecoder extends Converter { + const AuthorDecoder(); + + @override + Author convert(Map map) => AuthorSerializer.fromMap(map); +} + +class AuthorSerializer extends Codec { + const AuthorSerializer(); + + @override + AuthorEncoder get encoder => const AuthorEncoder(); + + @override + AuthorDecoder get decoder => const AuthorDecoder(); + + static Author fromMap(Map map) { + return Author( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String? ?? 'Tobe Osakwe'); + } + + static Map toMap(_Author? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name + }; + } +} + +abstract class AuthorFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/car.dart b/packages/orm/angel_orm_mysql/test/models/car.dart new file mode 100644 index 000000000..49c3ba97e --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/car.dart @@ -0,0 +1,17 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; +part 'car.g.dart'; + +@serializable +@orm +class _Car extends Model { + String? make; + String? description; + bool? familyFriendly; + DateTime? recalledAt; + double? price; +} diff --git a/packages/orm/angel_orm_mysql/test/models/car.g.dart b/packages/orm/angel_orm_mysql/test/models/car.g.dart new file mode 100644 index 000000000..ef982fb8c --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/car.g.dart @@ -0,0 +1,459 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'car.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class CarMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'cars', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'make', + length: 255, + ); + table.varChar( + 'description', + length: 255, + ); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('cars'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class CarQuery extends Query { + CarQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = CarQueryWhere(this); + } + + @override + final CarQueryValues values = CarQueryValues(); + + List _selectedFields = []; + + CarQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'cars'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + CarQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + CarQueryWhere? get where { + return _where; + } + + @override + CarQueryWhere newWhereClause() { + return CarQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Car( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String?) : null, + description: fields.contains('description') ? (row[4] as String?) : null, + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : null, + recalledAt: + fields.contains('recalled_at') ? mapToNullableDateTime(row[6]) : null, + price: fields.contains('price') ? mapToDouble(row[7]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class CarQueryWhere extends QueryWhere { + CarQueryWhere(CarQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]; + } +} + +class CarQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get make { + return (values['make'] as String?); + } + + set make(String? value) => values['make'] = value; + + String? get description { + return (values['description'] as String?); + } + + set description(String? value) => values['description'] = value; + + bool? get familyFriendly { + return (values['family_friendly'] as bool?); + } + + set familyFriendly(bool? value) => values['family_friendly'] = value; + + DateTime? get recalledAt { + return (values['recalled_at'] as DateTime?); + } + + set recalledAt(DateTime? value) => values['recalled_at'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + void copyFrom(Car model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Car extends _Car { + Car({ + this.id, + this.createdAt, + this.updatedAt, + this.make, + this.description, + this.familyFriendly, + this.recalledAt, + this.price, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? make; + + @override + String? description; + + @override + bool? familyFriendly; + + @override + DateTime? recalledAt; + + @override + double? price; + + Car copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + }) { + return Car( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price); + } + + @override + bool operator ==(other) { + return other is _Car && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]); + } + + @override + String toString() { + return 'Car(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price)'; + } + + Map toJson() { + return CarSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const CarSerializer carSerializer = CarSerializer(); + +class CarEncoder extends Converter { + const CarEncoder(); + + @override + Map convert(Car model) => CarSerializer.toMap(model); +} + +class CarDecoder extends Converter { + const CarDecoder(); + + @override + Car convert(Map map) => CarSerializer.fromMap(map); +} + +class CarSerializer extends Codec { + const CarSerializer(); + + @override + CarEncoder get encoder => const CarEncoder(); + + @override + CarDecoder get decoder => const CarDecoder(); + + static Car fromMap(Map map) { + return Car( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String?, + description: map['description'] as String?, + familyFriendly: map['family_friendly'] as bool?, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : null, + price: map['price'] as double?); + } + + static Map toMap(_Car? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt?.toIso8601String(), + 'price': model.price + }; + } +} + +abstract class CarFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/custom_expr.dart b/packages/orm/angel_orm_mysql/test/models/custom_expr.dart new file mode 100644 index 000000000..beef30a3e --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/custom_expr.dart @@ -0,0 +1,22 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'custom_expr.g.dart'; + +@serializable +@orm +class _Numbers extends Model { + @Column(expression: 'SELECT 2') + int? two; +} + +@serializable +@orm +class _Alphabet extends Model { + String? value; + + @belongsTo + _Numbers? numbers; +} diff --git a/packages/orm/angel_orm_mysql/test/models/custom_expr.g.dart b/packages/orm/angel_orm_mysql/test/models/custom_expr.g.dart new file mode 100644 index 000000000..30f95e63c --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/custom_expr.g.dart @@ -0,0 +1,692 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'custom_expr.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class NumbersMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'numbers', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('numbers'); + } +} + +class AlphabetMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'alphabets', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'value', + length: 255, + ); + table + .declare( + 'numbers_id', + ColumnType('int'), + ) + .references( + 'numbers', + 'id', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('alphabets'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class NumbersQuery extends Query { + NumbersQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['two'] = 'SELECT 2'; + _where = NumbersQueryWhere(this); + } + + @override + final NumbersQueryValues values = NumbersQueryValues(); + + List _selectedFields = []; + + NumbersQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'numbers'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'two', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + NumbersQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + NumbersQueryWhere? get where { + return _where; + } + + @override + NumbersQueryWhere newWhereClause() { + return NumbersQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Numbers( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + two: fields.contains('two') ? (row[3] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class NumbersQueryWhere extends QueryWhere { + NumbersQueryWhere(NumbersQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + ]; + } +} + +class NumbersQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + void copyFrom(Numbers model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + } +} + +class AlphabetQuery extends Query { + AlphabetQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AlphabetQueryWhere(this); + leftJoin( + _numbers = NumbersQuery( + trampoline: trampoline, + parent: this, + ), + 'numbers_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'two', + ], + trampoline: trampoline, + ); + } + + @override + final AlphabetQueryValues values = AlphabetQueryValues(); + + List _selectedFields = []; + + AlphabetQueryWhere? _where; + + late NumbersQuery _numbers; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'alphabets'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'value', + 'numbers_id', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AlphabetQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AlphabetQueryWhere? get where { + return _where; + } + + @override + AlphabetQueryWhere newWhereClause() { + return AlphabetQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Alphabet( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + value: fields.contains('value') ? (row[3] as String?) : null, + ); + if (row.length > 5) { + var modelOpt = NumbersQuery().parseRow(row.skip(5).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(numbers: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + NumbersQuery get numbers { + return _numbers; + } +} + +class AlphabetQueryWhere extends QueryWhere { + AlphabetQueryWhere(AlphabetQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + value = StringSqlExpressionBuilder( + query, + 'value', + ), + numbersId = NumericSqlExpressionBuilder( + query, + 'numbers_id', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder value; + + final NumericSqlExpressionBuilder numbersId; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + value, + numbersId, + ]; + } +} + +class AlphabetQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get value { + return (values['value'] as String?); + } + + set value(String? value) => values['value'] = value; + + int get numbersId { + return (values['numbers_id'] as int); + } + + set numbersId(int value) => values['numbers_id'] = value; + + void copyFrom(Alphabet model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + value = model.value; + if (model.numbers != null) { + values['numbers_id'] = model.numbers?.id; + } + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Numbers extends _Numbers { + Numbers({ + this.id, + this.createdAt, + this.updatedAt, + this.two, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? two; + + Numbers copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? two, + }) { + return Numbers( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + two: two ?? this.two); + } + + @override + bool operator ==(other) { + return other is _Numbers && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.two == two; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + two, + ]); + } + + @override + String toString() { + return 'Numbers(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, two=$two)'; + } + + Map toJson() { + return NumbersSerializer.toMap(this); + } +} + +@generatedSerializable +class Alphabet extends _Alphabet { + Alphabet({ + this.id, + this.createdAt, + this.updatedAt, + this.value, + this.numbers, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? value; + + @override + _Numbers? numbers; + + Alphabet copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? value, + _Numbers? numbers, + }) { + return Alphabet( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + value: value ?? this.value, + numbers: numbers ?? this.numbers); + } + + @override + bool operator ==(other) { + return other is _Alphabet && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.value == value && + other.numbers == numbers; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + value, + numbers, + ]); + } + + @override + String toString() { + return 'Alphabet(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, value=$value, numbers=$numbers)'; + } + + Map toJson() { + return AlphabetSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const NumbersSerializer numbersSerializer = NumbersSerializer(); + +class NumbersEncoder extends Converter { + const NumbersEncoder(); + + @override + Map convert(Numbers model) => NumbersSerializer.toMap(model); +} + +class NumbersDecoder extends Converter { + const NumbersDecoder(); + + @override + Numbers convert(Map map) => NumbersSerializer.fromMap(map); +} + +class NumbersSerializer extends Codec { + const NumbersSerializer(); + + @override + NumbersEncoder get encoder => const NumbersEncoder(); + + @override + NumbersDecoder get decoder => const NumbersDecoder(); + + static Numbers fromMap(Map map) { + return Numbers( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + two: map['two'] as int?); + } + + static Map toMap(_Numbers? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'two': model.two + }; + } +} + +abstract class NumbersFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + two, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String two = 'two'; +} + +const AlphabetSerializer alphabetSerializer = AlphabetSerializer(); + +class AlphabetEncoder extends Converter { + const AlphabetEncoder(); + + @override + Map convert(Alphabet model) => AlphabetSerializer.toMap(model); +} + +class AlphabetDecoder extends Converter { + const AlphabetDecoder(); + + @override + Alphabet convert(Map map) => AlphabetSerializer.fromMap(map); +} + +class AlphabetSerializer extends Codec { + const AlphabetSerializer(); + + @override + AlphabetEncoder get encoder => const AlphabetEncoder(); + + @override + AlphabetDecoder get decoder => const AlphabetDecoder(); + + static Alphabet fromMap(Map map) { + return Alphabet( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + value: map['value'] as String?, + numbers: map['numbers'] != null + ? NumbersSerializer.fromMap(map['numbers'] as Map) + : null); + } + + static Map toMap(_Alphabet? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'value': model.value, + 'numbers': NumbersSerializer.toMap(model.numbers) + }; + } +} + +abstract class AlphabetFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + value, + numbers, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String value = 'value'; + + static const String numbers = 'numbers'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/email_indexed.dart b/packages/orm/angel_orm_mysql/test/models/email_indexed.dart new file mode 100644 index 000000000..928bb7f48 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/email_indexed.dart @@ -0,0 +1,41 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'email_indexed.g.dart'; + +// * https://github.com/angel-dart/angel/issues/116 + +@serializable +@orm +abstract class _Role { + @PrimaryKey(columnType: ColumnType.varChar) + String? get role; + + @ManyToMany(_RoleUser) + List<_User> get users; +} + +@serializable +@orm +abstract class _RoleUser { + @belongsTo + _Role? get role; + + @belongsTo + _User? get user; +} + +@serializable +@orm +abstract class _User { + // @PrimaryKey(columnType: ColumnType.varChar) + @primaryKey + String? get email; + String? get name; + String? get password; + + @ManyToMany(_RoleUser) + List<_Role> get roles; +} diff --git a/packages/orm/angel_orm_mysql/test/models/email_indexed.g.dart b/packages/orm/angel_orm_mysql/test/models/email_indexed.g.dart new file mode 100644 index 000000000..8a2b239d0 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/email_indexed.g.dart @@ -0,0 +1,991 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'email_indexed.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class RoleMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'roles', + (table) { + table + .varChar( + 'role', + length: 255, + ) + .primaryKey(); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'roles', + cascade: true, + ); + } +} + +class RoleUserMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'role_users', + (table) { + table + .declare( + 'role_role', + ColumnType('varchar'), + ) + .references( + 'roles', + 'role', + ); + table + .declare( + 'user_email', + ColumnType('varchar'), + ) + .references( + 'users', + 'email', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('role_users'); + } +} + +class UserMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'users', + (table) { + table + .varChar( + 'email', + length: 255, + ) + .primaryKey(); + table.varChar( + 'name', + length: 255, + ); + table.varChar( + 'password', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'users', + cascade: true, + ); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class RoleQuery extends Query { + RoleQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleQueryWhere(this); + leftJoin( + '(SELECT role_users.role_role, users.email, users.name, users.password FROM users LEFT JOIN role_users ON role_users.user_email=users.email)', + 'role', + 'role_role', + additionalFields: const [ + 'email', + 'name', + 'password', + ], + trampoline: trampoline, + ); + } + + @override + final RoleQueryValues values = RoleQueryValues(); + + List _selectedFields = []; + + RoleQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'roles'; + } + + @override + List get fields { + const _fields = ['role']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleQueryWhere? get where { + return _where; + } + + @override + RoleQueryWhere newWhereClause() { + return RoleQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = + Role(role: fields.contains('role') ? (row[0] as String?) : null); + if (row.length > 1) { + var modelOpt = UserQuery().parseRow(row.skip(1).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(users: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('roles') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } +} + +class RoleQueryWhere extends QueryWhere { + RoleQueryWhere(RoleQuery query) + : role = StringSqlExpressionBuilder( + query, + 'role', + ); + + final StringSqlExpressionBuilder role; + + @override + List get expressionBuilders { + return [role]; + } +} + +class RoleQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get role { + return (values['role'] as String?); + } + + set role(String? value) => values['role'] = value; + + void copyFrom(Role model) { + role = model.role; + } +} + +class RoleUserQuery extends Query { + RoleUserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleUserQueryWhere(this); + leftJoin( + _role = RoleQuery( + trampoline: trampoline, + parent: this, + ), + 'role_role', + 'role', + additionalFields: const ['role'], + trampoline: trampoline, + ); + leftJoin( + _user = UserQuery( + trampoline: trampoline, + parent: this, + ), + 'user_email', + 'email', + additionalFields: const [ + 'email', + 'name', + 'password', + ], + trampoline: trampoline, + ); + } + + @override + final RoleUserQueryValues values = RoleUserQueryValues(); + + List _selectedFields = []; + + RoleUserQueryWhere? _where; + + late RoleQuery _role; + + late UserQuery _user; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'role_users'; + } + + @override + List get fields { + const _fields = [ + 'role_role', + 'user_email', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleUserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleUserQueryWhere? get where { + return _where; + } + + @override + RoleUserQueryWhere newWhereClause() { + return RoleUserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = RoleUser(); + if (row.length > 2) { + var modelOpt = RoleQuery().parseRow(row.skip(2).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(role: m); + }); + } + if (row.length > 3) { + var modelOpt = UserQuery().parseRow(row.skip(3).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(user: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + RoleQuery get role { + return _role; + } + + UserQuery get user { + return _user; + } +} + +class RoleUserQueryWhere extends QueryWhere { + RoleUserQueryWhere(RoleUserQuery query) + : roleRole = StringSqlExpressionBuilder( + query, + 'role_role', + ), + userEmail = StringSqlExpressionBuilder( + query, + 'user_email', + ); + + final StringSqlExpressionBuilder roleRole; + + final StringSqlExpressionBuilder userEmail; + + @override + List get expressionBuilders { + return [ + roleRole, + userEmail, + ]; + } +} + +class RoleUserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get roleRole { + return (values['role_role'] as String?); + } + + set roleRole(String? value) => values['role_role'] = value; + + String? get userEmail { + return (values['user_email'] as String?); + } + + set userEmail(String? value) => values['user_email'] = value; + + void copyFrom(RoleUser model) { + if (model.role != null) { + values['role_role'] = model.role?.role; + } + if (model.user != null) { + values['user_email'] = model.user?.email; + } + } +} + +class UserQuery extends Query { + UserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UserQueryWhere(this); + leftJoin( + '(SELECT role_users.user_email, roles.role FROM roles LEFT JOIN role_users ON role_users.role_role=roles.role)', + 'email', + 'user_email', + additionalFields: const ['role'], + trampoline: trampoline, + ); + } + + @override + final UserQueryValues values = UserQueryValues(); + + List _selectedFields = []; + + UserQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'users'; + } + + @override + List get fields { + const _fields = [ + 'email', + 'name', + 'password', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UserQueryWhere? get where { + return _where; + } + + @override + UserQueryWhere newWhereClause() { + return UserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = User( + email: fields.contains('email') ? (row[0] as String?) : null, + name: fields.contains('name') ? (row[1] as String?) : null, + password: fields.contains('password') ? (row[2] as String?) : null, + ); + if (row.length > 3) { + var modelOpt = RoleQuery().parseRow(row.skip(3).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(roles: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('users') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } +} + +class UserQueryWhere extends QueryWhere { + UserQueryWhere(UserQuery query) + : email = StringSqlExpressionBuilder( + query, + 'email', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + password = StringSqlExpressionBuilder( + query, + 'password', + ); + + final StringSqlExpressionBuilder email; + + final StringSqlExpressionBuilder name; + + final StringSqlExpressionBuilder password; + + @override + List get expressionBuilders { + return [ + email, + name, + password, + ]; + } +} + +class UserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get email { + return (values['email'] as String?); + } + + set email(String? value) => values['email'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + String? get password { + return (values['password'] as String?); + } + + set password(String? value) => values['password'] = value; + + void copyFrom(User model) { + email = model.email; + name = model.name; + password = model.password; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Role implements _Role { + Role({ + this.role, + this.users = const [], + }); + + @override + String? role; + + @override + List<_User> users; + + Role copyWith({ + String? role, + List<_User>? users, + }) { + return Role(role: role ?? this.role, users: users ?? this.users); + } + + @override + bool operator ==(other) { + return other is _Role && + other.role == role && + ListEquality<_User>(DefaultEquality<_User>()) + .equals(other.users, users); + } + + @override + int get hashCode { + return hashObjects([ + role, + users, + ]); + } + + @override + String toString() { + return 'Role(role=$role, users=$users)'; + } + + Map toJson() { + return RoleSerializer.toMap(this); + } +} + +@generatedSerializable +class RoleUser implements _RoleUser { + RoleUser({ + this.role, + this.user, + }); + + @override + _Role? role; + + @override + _User? user; + + RoleUser copyWith({ + _Role? role, + _User? user, + }) { + return RoleUser(role: role ?? this.role, user: user ?? this.user); + } + + @override + bool operator ==(other) { + return other is _RoleUser && other.role == role && other.user == user; + } + + @override + int get hashCode { + return hashObjects([ + role, + user, + ]); + } + + @override + String toString() { + return 'RoleUser(role=$role, user=$user)'; + } + + Map toJson() { + return RoleUserSerializer.toMap(this); + } +} + +@generatedSerializable +class User implements _User { + User({ + this.email, + this.name, + this.password, + this.roles = const [], + }); + + @override + String? email; + + @override + String? name; + + @override + String? password; + + @override + List<_Role> roles; + + User copyWith({ + String? email, + String? name, + String? password, + List<_Role>? roles, + }) { + return User( + email: email ?? this.email, + name: name ?? this.name, + password: password ?? this.password, + roles: roles ?? this.roles); + } + + @override + bool operator ==(other) { + return other is _User && + other.email == email && + other.name == name && + other.password == password && + ListEquality<_Role>(DefaultEquality<_Role>()) + .equals(other.roles, roles); + } + + @override + int get hashCode { + return hashObjects([ + email, + name, + password, + roles, + ]); + } + + @override + String toString() { + return 'User(email=$email, name=$name, password=$password, roles=$roles)'; + } + + Map toJson() { + return UserSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const RoleSerializer roleSerializer = RoleSerializer(); + +class RoleEncoder extends Converter { + const RoleEncoder(); + + @override + Map convert(Role model) => RoleSerializer.toMap(model); +} + +class RoleDecoder extends Converter { + const RoleDecoder(); + + @override + Role convert(Map map) => RoleSerializer.fromMap(map); +} + +class RoleSerializer extends Codec { + const RoleSerializer(); + + @override + RoleEncoder get encoder => const RoleEncoder(); + + @override + RoleDecoder get decoder => const RoleDecoder(); + + static Role fromMap(Map map) { + return Role( + role: map['role'] as String?, + users: map['users'] is Iterable + ? List.unmodifiable(((map['users'] as Iterable).whereType()) + .map(UserSerializer.fromMap)) + : []); + } + + static Map toMap(_Role? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': model.role, + 'users': model.users.map((m) => UserSerializer.toMap(m)).toList() + }; + } +} + +abstract class RoleFields { + static const List allFields = [ + role, + users, + ]; + + static const String role = 'role'; + + static const String users = 'users'; +} + +const RoleUserSerializer roleUserSerializer = RoleUserSerializer(); + +class RoleUserEncoder extends Converter { + const RoleUserEncoder(); + + @override + Map convert(RoleUser model) => RoleUserSerializer.toMap(model); +} + +class RoleUserDecoder extends Converter { + const RoleUserDecoder(); + + @override + RoleUser convert(Map map) => RoleUserSerializer.fromMap(map); +} + +class RoleUserSerializer extends Codec { + const RoleUserSerializer(); + + @override + RoleUserEncoder get encoder => const RoleUserEncoder(); + + @override + RoleUserDecoder get decoder => const RoleUserDecoder(); + + static RoleUser fromMap(Map map) { + return RoleUser( + role: map['role'] != null + ? RoleSerializer.fromMap(map['role'] as Map) + : null, + user: map['user'] != null + ? UserSerializer.fromMap(map['user'] as Map) + : null); + } + + static Map toMap(_RoleUser? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': RoleSerializer.toMap(model.role), + 'user': UserSerializer.toMap(model.user) + }; + } +} + +abstract class RoleUserFields { + static const List allFields = [ + role, + user, + ]; + + static const String role = 'role'; + + static const String user = 'user'; +} + +const UserSerializer userSerializer = UserSerializer(); + +class UserEncoder extends Converter { + const UserEncoder(); + + @override + Map convert(User model) => UserSerializer.toMap(model); +} + +class UserDecoder extends Converter { + const UserDecoder(); + + @override + User convert(Map map) => UserSerializer.fromMap(map); +} + +class UserSerializer extends Codec { + const UserSerializer(); + + @override + UserEncoder get encoder => const UserEncoder(); + + @override + UserDecoder get decoder => const UserDecoder(); + + static User fromMap(Map map) { + return User( + email: map['email'] as String?, + name: map['name'] as String?, + password: map['password'] as String?, + roles: map['roles'] is Iterable + ? List.unmodifiable(((map['roles'] as Iterable).whereType()) + .map(RoleSerializer.fromMap)) + : []); + } + + static Map toMap(_User? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'email': model.email, + 'name': model.name, + 'password': model.password, + 'roles': model.roles.map((m) => RoleSerializer.toMap(m)).toList() + }; + } +} + +abstract class UserFields { + static const List allFields = [ + email, + name, + password, + roles, + ]; + + static const String email = 'email'; + + static const String name = 'name'; + + static const String password = 'password'; + + static const String roles = 'roles'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/fortune.dart b/packages/orm/angel_orm_mysql/test/models/fortune.dart new file mode 100644 index 000000000..6e4f49af2 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/fortune.dart @@ -0,0 +1,16 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:optional/optional.dart'; + +part 'fortune.g.dart'; + +@serializable +@Orm(tableName: 'fortune') +abstract class _Fortune { + @primaryKey + int? id; + + @Column(length: 2048) + String? message; +} diff --git a/packages/orm/angel_orm_mysql/test/models/fortune.g.dart b/packages/orm/angel_orm_mysql/test/models/fortune.g.dart new file mode 100644 index 000000000..ed9290f6d --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/fortune.g.dart @@ -0,0 +1,249 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fortune.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class FortuneMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'fortune', + (table) { + table.integer('id').primaryKey(); + table.varChar( + 'message', + length: 2048, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('fortune'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class FortuneQuery extends Query { + FortuneQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FortuneQueryWhere(this); + } + + @override + final FortuneQueryValues values = FortuneQueryValues(); + + List _selectedFields = []; + + FortuneQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'fortune'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'message', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FortuneQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FortuneQueryWhere? get where { + return _where; + } + + @override + FortuneQueryWhere newWhereClause() { + return FortuneQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Fortune( + id: fields.contains('id') ? (row[0] as int?) : null, + message: fields.contains('message') ? (row[1] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FortuneQueryWhere extends QueryWhere { + FortuneQueryWhere(FortuneQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + message = StringSqlExpressionBuilder( + query, + 'message', + ); + + final NumericSqlExpressionBuilder id; + + final StringSqlExpressionBuilder message; + + @override + List get expressionBuilders { + return [ + id, + message, + ]; + } +} + +class FortuneQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get id { + return (values['id'] as int?); + } + + set id(int? value) => values['id'] = value; + + String? get message { + return (values['message'] as String?); + } + + set message(String? value) => values['message'] = value; + + void copyFrom(Fortune model) { + id = model.id; + message = model.message; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Fortune extends _Fortune { + Fortune({ + this.id, + this.message, + }); + + @override + int? id; + + @override + String? message; + + Fortune copyWith({ + int? id, + String? message, + }) { + return Fortune(id: id ?? this.id, message: message ?? this.message); + } + + @override + bool operator ==(other) { + return other is _Fortune && other.id == id && other.message == message; + } + + @override + int get hashCode { + return hashObjects([ + id, + message, + ]); + } + + @override + String toString() { + return 'Fortune(id=$id, message=$message)'; + } + + Map toJson() { + return FortuneSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const FortuneSerializer fortuneSerializer = FortuneSerializer(); + +class FortuneEncoder extends Converter { + const FortuneEncoder(); + + @override + Map convert(Fortune model) => FortuneSerializer.toMap(model); +} + +class FortuneDecoder extends Converter { + const FortuneDecoder(); + + @override + Fortune convert(Map map) => FortuneSerializer.fromMap(map); +} + +class FortuneSerializer extends Codec { + const FortuneSerializer(); + + @override + FortuneEncoder get encoder => const FortuneEncoder(); + + @override + FortuneDecoder get decoder => const FortuneDecoder(); + + static Fortune fromMap(Map map) { + return Fortune(id: map['id'] as int?, message: map['message'] as String?); + } + + static Map toMap(_Fortune? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'message': model.message}; + } +} + +abstract class FortuneFields { + static const List allFields = [ + id, + message, + ]; + + static const String id = 'id'; + + static const String message = 'message'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/has_car.dart b/packages/orm/angel_orm_mysql/test/models/has_car.dart new file mode 100644 index 000000000..d5ea3bc3c --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/has_car.dart @@ -0,0 +1,48 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'has_car.g.dart'; + +// Map _carToMap(Car car) => car.toJson(); + +// Car _carFromMap(map) => CarSerializer.fromMap(map as Map); + +enum CarType { sedan, suv, atv } + +Color? codeToColor(String? code) => code == null + ? null + : Color.values.firstWhere((color) => color.code == code); + +String? colorToCode(Color? color) => color?.code; + +enum Color { + red('R'), + green('G'), + blue('B'); + + const Color(this.code); + + final String code; +} + +@orm +@serializable +abstract class _HasCar extends Model { + // TODO: Do this without explicit serializers + // @SerializableField( + // serializesTo: Map, serializer: #_carToMap, deserializer: #_carFromMap) + // Car get car; + + @SerializableField(isNullable: false, defaultValue: CarType.sedan) + CarType? get type; + + @SerializableField( + serializesTo: String, + serializer: #colorToCode, + deserializer: #codeToColor, + ) + @Column(type: ColumnType.varChar, length: 1) + Color? color; +} diff --git a/packages/orm/angel_orm_mysql/test/models/has_car.g.dart b/packages/orm/angel_orm_mysql/test/models/has_car.g.dart new file mode 100644 index 000000000..117eae249 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/has_car.g.dart @@ -0,0 +1,376 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'has_car.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class HasCarMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'has_cars', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.declareColumn( + 'color', + Column( + type: ColumnType('varchar'), + length: 1, + ), + ); + table.integer('type').defaultsTo(0); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('has_cars'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class HasCarQuery extends Query { + HasCarQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = HasCarQueryWhere(this); + } + + @override + final HasCarQueryValues values = HasCarQueryValues(); + + List _selectedFields = []; + + HasCarQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'has_cars'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'color', + 'type', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + HasCarQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + HasCarQueryWhere? get where { + return _where; + } + + @override + HasCarQueryWhere newWhereClause() { + return HasCarQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = HasCar( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + color: fields.contains('color') + ? row[3] == null + ? null + : codeToColor((row[3] as String)) + : null, + type: fields.contains('type') + ? row[4] == null + ? null + : CarType.values[(row[4] as int)] + : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class HasCarQueryWhere extends QueryWhere { + HasCarQueryWhere(HasCarQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + color = StringSqlExpressionBuilder( + query, + 'color', + ), + type = EnumSqlExpressionBuilder( + query, + 'type', + (v) => v?.index as int, + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder color; + + final EnumSqlExpressionBuilder type; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + color, + type, + ]; + } +} + +class HasCarQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + Color? get color { + return codeToColor((values['color'] as String)); + } + + set color(Color? value) => values['color'] = colorToCode(value); + + CarType? get type { + return CarType.values[(values['type'] as int)]; + } + + set type(CarType? value) => values['type'] = value?.index; + + void copyFrom(HasCar model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + color = model.color; + type = model.type; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class HasCar extends _HasCar { + HasCar({ + this.id, + this.createdAt, + this.updatedAt, + this.color, + this.type = CarType.sedan, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + Color? color; + + @override + CarType? type; + + HasCar copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Color? color, + CarType? type, + }) { + return HasCar( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + color: color ?? this.color, + type: type ?? this.type); + } + + @override + bool operator ==(other) { + return other is _HasCar && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.color == color && + other.type == type; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + color, + type, + ]); + } + + @override + String toString() { + return 'HasCar(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, color=$color, type=$type)'; + } + + Map toJson() { + return HasCarSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const HasCarSerializer hasCarSerializer = HasCarSerializer(); + +class HasCarEncoder extends Converter { + const HasCarEncoder(); + + @override + Map convert(HasCar model) => HasCarSerializer.toMap(model); +} + +class HasCarDecoder extends Converter { + const HasCarDecoder(); + + @override + HasCar convert(Map map) => HasCarSerializer.fromMap(map); +} + +class HasCarSerializer extends Codec { + const HasCarSerializer(); + + @override + HasCarEncoder get encoder => const HasCarEncoder(); + + @override + HasCarDecoder get decoder => const HasCarDecoder(); + + static HasCar fromMap(Map map) { + if (map['type'] == null) { + throw FormatException("Missing required field 'type' on HasCar."); + } + + return HasCar( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + color: codeToColor(map['color']), + type: map['type'] as CarType? ?? CarType.sedan); + } + + static Map toMap(_HasCar? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'color': colorToCode(model.color), + 'type': model.type + }; + } +} + +abstract class HasCarFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + color, + type, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String color = 'color'; + + static const String type = 'type'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/has_map.dart b/packages/orm/angel_orm_mysql/test/models/has_map.dart new file mode 100644 index 000000000..02e6ccb59 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/has_map.dart @@ -0,0 +1,24 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'has_map.g.dart'; + +// String _boolToCustom(bool v) => v ? 'yes' : 'no'; +// bool _customToBool(v) => v == 'yes'; + +@orm +@serializable +abstract class _HasMap { + Map? get value; + + List? get list; + + // TODO: Support custom serializers + // @SerializableField( + // serializer: #_boolToCustom, + // deserializer: #_customToBool, + // serializesTo: String) + // bool get customBool; +} diff --git a/packages/orm/angel_orm_mysql/test/models/has_map.g.dart b/packages/orm/angel_orm_mysql/test/models/has_map.g.dart new file mode 100644 index 000000000..9f17a9228 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/has_map.g.dart @@ -0,0 +1,269 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'has_map.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class HasMapMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'has_maps', + (table) { + table.declareColumn( + 'value', + Column( + type: ColumnType('jsonb'), + length: 255, + ), + ); + table.declareColumn( + 'list', + Column( + type: ColumnType('jsonb'), + length: 255, + ), + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('has_maps'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class HasMapQuery extends Query { + HasMapQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = HasMapQueryWhere(this); + } + + @override + final HasMapQueryValues values = HasMapQueryValues(); + + List _selectedFields = []; + + HasMapQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'has_maps'; + } + + @override + List get fields { + const _fields = [ + 'value', + 'list', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + HasMapQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + HasMapQueryWhere? get where { + return _where; + } + + @override + HasMapQueryWhere newWhereClause() { + return HasMapQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = HasMap( + value: + fields.contains('value') ? (row[0] as Map?) : null, + list: fields.contains('list') ? (row[1] as List?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class HasMapQueryWhere extends QueryWhere { + HasMapQueryWhere(HasMapQuery query) + : value = MapSqlExpressionBuilder( + query, + 'value', + ), + list = ListSqlExpressionBuilder( + query, + 'list', + ); + + final MapSqlExpressionBuilder value; + + final ListSqlExpressionBuilder list; + + @override + List get expressionBuilders { + return [ + value, + list, + ]; + } +} + +class HasMapQueryValues extends MapQueryValues { + @override + Map get casts { + return {'list': 'jsonb'}; + } + + Map? get value { + return (values['value'] as Map?); + } + + set value(Map? value) => values['value'] = value; + + List? get list { + return json.decode((values['list'] as String)).cast(); + } + + set list(List? value) => values['list'] = json.encode(value); + + void copyFrom(HasMap model) { + value = model.value; + list = model.list; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class HasMap implements _HasMap { + HasMap({ + this.value, + this.list = const [], + }); + + @override + Map? value; + + @override + List? list; + + HasMap copyWith({ + Map? value, + List? list, + }) { + return HasMap(value: value ?? this.value, list: list ?? this.list); + } + + @override + bool operator ==(other) { + return other is _HasMap && + MapEquality( + keys: DefaultEquality(), values: DefaultEquality()) + .equals(other.value, value) && + ListEquality(DefaultEquality()).equals(other.list, list); + } + + @override + int get hashCode { + return hashObjects([ + value, + list, + ]); + } + + @override + String toString() { + return 'HasMap(value=$value, list=$list)'; + } + + Map toJson() { + return HasMapSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const HasMapSerializer hasMapSerializer = HasMapSerializer(); + +class HasMapEncoder extends Converter { + const HasMapEncoder(); + + @override + Map convert(HasMap model) => HasMapSerializer.toMap(model); +} + +class HasMapDecoder extends Converter { + const HasMapDecoder(); + + @override + HasMap convert(Map map) => HasMapSerializer.fromMap(map); +} + +class HasMapSerializer extends Codec { + const HasMapSerializer(); + + @override + HasMapEncoder get encoder => const HasMapEncoder(); + + @override + HasMapDecoder get decoder => const HasMapDecoder(); + + static HasMap fromMap(Map map) { + return HasMap( + value: map['value'] is Map + ? (map['value'] as Map).cast() + : {}, + list: map['list'] is Iterable + ? (map['list'] as Iterable).cast().toList() + : []); + } + + static Map toMap(_HasMap? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'value': model.value, 'list': model.list}; + } +} + +abstract class HasMapFields { + static const List allFields = [ + value, + list, + ]; + + static const String value = 'value'; + + static const String list = 'list'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/leg.dart b/packages/orm/angel_orm_mysql/test/models/leg.dart new file mode 100644 index 000000000..f1e4c5f60 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/leg.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'leg.g.dart'; + +@serializable +@orm +class _Leg extends Model { + @hasOne + _Foot? foot; + + String? name; +} + +@serializable +@Orm(tableName: 'feet') +class _Foot extends Model { + int? legId; + + double? nToes; +} diff --git a/packages/orm/angel_orm_mysql/test/models/leg.g.dart b/packages/orm/angel_orm_mysql/test/models/leg.g.dart new file mode 100644 index 000000000..532ee5160 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/leg.g.dart @@ -0,0 +1,714 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'leg.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class LegMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'legs', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'name', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'legs', + cascade: true, + ); + } +} + +class FootMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'feet', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('leg_id'); + table.double('n_toes'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('feet'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class LegQuery extends Query { + LegQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = LegQueryWhere(this); + leftJoin( + _foot = FootQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'leg_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'leg_id', + 'n_toes', + ], + trampoline: trampoline, + ); + } + + @override + final LegQueryValues values = LegQueryValues(); + + List _selectedFields = []; + + LegQueryWhere? _where; + + late FootQuery _foot; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'legs'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + LegQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + LegQueryWhere? get where { + return _where; + } + + @override + LegQueryWhere newWhereClause() { + return LegQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Leg( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + ); + if (row.length > 4) { + var modelOpt = FootQuery().parseRow(row.skip(4).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foot: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + FootQuery get foot { + return _foot; + } +} + +class LegQueryWhere extends QueryWhere { + LegQueryWhere(LegQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + ]; + } +} + +class LegQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Leg model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + } +} + +class FootQuery extends Query { + FootQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FootQueryWhere(this); + } + + @override + final FootQueryValues values = FootQueryValues(); + + List _selectedFields = []; + + FootQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'feet'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'leg_id', + 'n_toes', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FootQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FootQueryWhere? get where { + return _where; + } + + @override + FootQueryWhere newWhereClause() { + return FootQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Foot( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + legId: fields.contains('leg_id') ? (row[3] as int?) : null, + nToes: fields.contains('n_toes') ? mapToDouble(row[4]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FootQueryWhere extends QueryWhere { + FootQueryWhere(FootQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + legId = NumericSqlExpressionBuilder( + query, + 'leg_id', + ), + nToes = NumericSqlExpressionBuilder( + query, + 'n_toes', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder legId; + + final NumericSqlExpressionBuilder nToes; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + legId, + nToes, + ]; + } +} + +class FootQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get legId { + return (values['leg_id'] as int?); + } + + set legId(int? value) => values['leg_id'] = value; + + double? get nToes { + return (values['n_toes'] as double?) ?? 0.0; + } + + set nToes(double? value) => values['n_toes'] = value; + + void copyFrom(Foot model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + legId = model.legId; + nToes = model.nToes; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Leg extends _Leg { + Leg({ + this.id, + this.createdAt, + this.updatedAt, + this.foot, + this.name, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Foot? foot; + + @override + String? name; + + Leg copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Foot? foot, + String? name, + }) { + return Leg( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + foot: foot ?? this.foot, + name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Leg && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.foot == foot && + other.name == name; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + foot, + name, + ]); + } + + @override + String toString() { + return 'Leg(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, foot=$foot, name=$name)'; + } + + Map toJson() { + return LegSerializer.toMap(this); + } +} + +@generatedSerializable +class Foot extends _Foot { + Foot({ + this.id, + this.createdAt, + this.updatedAt, + this.legId, + this.nToes, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? legId; + + @override + double? nToes; + + Foot copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? legId, + double? nToes, + }) { + return Foot( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + legId: legId ?? this.legId, + nToes: nToes ?? this.nToes); + } + + @override + bool operator ==(other) { + return other is _Foot && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.legId == legId && + other.nToes == nToes; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + legId, + nToes, + ]); + } + + @override + String toString() { + return 'Foot(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, legId=$legId, nToes=$nToes)'; + } + + Map toJson() { + return FootSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const LegSerializer legSerializer = LegSerializer(); + +class LegEncoder extends Converter { + const LegEncoder(); + + @override + Map convert(Leg model) => LegSerializer.toMap(model); +} + +class LegDecoder extends Converter { + const LegDecoder(); + + @override + Leg convert(Map map) => LegSerializer.fromMap(map); +} + +class LegSerializer extends Codec { + const LegSerializer(); + + @override + LegEncoder get encoder => const LegEncoder(); + + @override + LegDecoder get decoder => const LegDecoder(); + + static Leg fromMap(Map map) { + return Leg( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + foot: map['foot'] != null + ? FootSerializer.fromMap(map['foot'] as Map) + : null, + name: map['name'] as String?); + } + + static Map toMap(_Leg? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'foot': FootSerializer.toMap(model.foot), + 'name': model.name + }; + } +} + +abstract class LegFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + foot, + name, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String foot = 'foot'; + + static const String name = 'name'; +} + +const FootSerializer footSerializer = FootSerializer(); + +class FootEncoder extends Converter { + const FootEncoder(); + + @override + Map convert(Foot model) => FootSerializer.toMap(model); +} + +class FootDecoder extends Converter { + const FootDecoder(); + + @override + Foot convert(Map map) => FootSerializer.fromMap(map); +} + +class FootSerializer extends Codec { + const FootSerializer(); + + @override + FootEncoder get encoder => const FootEncoder(); + + @override + FootDecoder get decoder => const FootDecoder(); + + static Foot fromMap(Map map) { + return Foot( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + legId: map['leg_id'] as int?, + nToes: map['n_toes'] as double?); + } + + static Map toMap(_Foot? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'leg_id': model.legId, + 'n_toes': model.nToes + }; + } +} + +abstract class FootFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + legId, + nToes, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String legId = 'leg_id'; + + static const String nToes = 'n_toes'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/order.dart b/packages/orm/angel_orm_mysql/test/models/order.dart new file mode 100644 index 000000000..595345b2b --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/order.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'order.g.dart'; + +@orm +@serializable +abstract class _Order extends Model { + @belongsTo + _Customer? get customer; + + int? get employeeId; + + DateTime? get orderDate; + + int? get shipperId; +} + +@orm +@serializable +class _Customer extends Model {} diff --git a/packages/orm/angel_orm_mysql/test/models/order.g.dart b/packages/orm/angel_orm_mysql/test/models/order.g.dart new file mode 100644 index 000000000..43627c213 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/order.g.dart @@ -0,0 +1,737 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'order.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class OrderMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'orders', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('employee_id'); + table.timeStamp('order_date'); + table.integer('shipper_id'); + table + .declare( + 'customer_id', + ColumnType('int'), + ) + .references( + 'customers', + 'id', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('orders'); + } +} + +class CustomerMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'customers', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('customers'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class OrderQuery extends Query { + OrderQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = OrderQueryWhere(this); + leftJoin( + _customer = CustomerQuery( + trampoline: trampoline, + parent: this, + ), + 'customer_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + ], + trampoline: trampoline, + ); + } + + @override + final OrderQueryValues values = OrderQueryValues(); + + List _selectedFields = []; + + OrderQueryWhere? _where; + + late CustomerQuery _customer; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'orders'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'customer_id', + 'employee_id', + 'order_date', + 'shipper_id', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + OrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + OrderQueryWhere? get where { + return _where; + } + + @override + OrderQueryWhere newWhereClause() { + return OrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Order( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + employeeId: fields.contains('employee_id') ? (row[4] as int?) : null, + orderDate: + fields.contains('order_date') ? mapToNullableDateTime(row[5]) : null, + shipperId: fields.contains('shipper_id') ? (row[6] as int?) : null, + ); + if (row.length > 7) { + var modelOpt = CustomerQuery().parseRow(row.skip(7).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(customer: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + CustomerQuery get customer { + return _customer; + } +} + +class OrderQueryWhere extends QueryWhere { + OrderQueryWhere(OrderQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + customerId = NumericSqlExpressionBuilder( + query, + 'customer_id', + ), + employeeId = NumericSqlExpressionBuilder( + query, + 'employee_id', + ), + orderDate = DateTimeSqlExpressionBuilder( + query, + 'order_date', + ), + shipperId = NumericSqlExpressionBuilder( + query, + 'shipper_id', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder customerId; + + final NumericSqlExpressionBuilder employeeId; + + final DateTimeSqlExpressionBuilder orderDate; + + final NumericSqlExpressionBuilder shipperId; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + customerId, + employeeId, + orderDate, + shipperId, + ]; + } +} + +class OrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int get customerId { + return (values['customer_id'] as int); + } + + set customerId(int value) => values['customer_id'] = value; + + int? get employeeId { + return (values['employee_id'] as int?); + } + + set employeeId(int? value) => values['employee_id'] = value; + + DateTime? get orderDate { + return (values['order_date'] as DateTime?); + } + + set orderDate(DateTime? value) => values['order_date'] = value; + + int? get shipperId { + return (values['shipper_id'] as int?); + } + + set shipperId(int? value) => values['shipper_id'] = value; + + void copyFrom(Order model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + employeeId = model.employeeId; + orderDate = model.orderDate; + shipperId = model.shipperId; + if (model.customer != null) { + values['customer_id'] = model.customer?.id; + } + } +} + +class CustomerQuery extends Query { + CustomerQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = CustomerQueryWhere(this); + } + + @override + final CustomerQueryValues values = CustomerQueryValues(); + + List _selectedFields = []; + + CustomerQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'customers'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + CustomerQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + CustomerQueryWhere? get where { + return _where; + } + + @override + CustomerQueryWhere newWhereClause() { + return CustomerQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Customer( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class CustomerQueryWhere extends QueryWhere { + CustomerQueryWhere(CustomerQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + ]; + } +} + +class CustomerQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + void copyFrom(Customer model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Order extends _Order { + Order({ + this.id, + this.createdAt, + this.updatedAt, + this.customer, + this.employeeId, + this.orderDate, + this.shipperId, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Customer? customer; + + @override + int? employeeId; + + @override + DateTime? orderDate; + + @override + int? shipperId; + + Order copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Customer? customer, + int? employeeId, + DateTime? orderDate, + int? shipperId, + }) { + return Order( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + customer: customer ?? this.customer, + employeeId: employeeId ?? this.employeeId, + orderDate: orderDate ?? this.orderDate, + shipperId: shipperId ?? this.shipperId); + } + + @override + bool operator ==(other) { + return other is _Order && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.customer == customer && + other.employeeId == employeeId && + other.orderDate == orderDate && + other.shipperId == shipperId; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + customer, + employeeId, + orderDate, + shipperId, + ]); + } + + @override + String toString() { + return 'Order(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, customer=$customer, employeeId=$employeeId, orderDate=$orderDate, shipperId=$shipperId)'; + } + + Map toJson() { + return OrderSerializer.toMap(this); + } +} + +@generatedSerializable +class Customer extends _Customer { + Customer({ + this.id, + this.createdAt, + this.updatedAt, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + Customer copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return Customer( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt); + } + + @override + bool operator ==(other) { + return other is _Customer && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + ]); + } + + @override + String toString() { + return 'Customer(id=$id, createdAt=$createdAt, updatedAt=$updatedAt)'; + } + + Map toJson() { + return CustomerSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const OrderSerializer orderSerializer = OrderSerializer(); + +class OrderEncoder extends Converter { + const OrderEncoder(); + + @override + Map convert(Order model) => OrderSerializer.toMap(model); +} + +class OrderDecoder extends Converter { + const OrderDecoder(); + + @override + Order convert(Map map) => OrderSerializer.fromMap(map); +} + +class OrderSerializer extends Codec { + const OrderSerializer(); + + @override + OrderEncoder get encoder => const OrderEncoder(); + + @override + OrderDecoder get decoder => const OrderDecoder(); + + static Order fromMap(Map map) { + return Order( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + customer: map['customer'] != null + ? CustomerSerializer.fromMap(map['customer'] as Map) + : null, + employeeId: map['employee_id'] as int?, + orderDate: map['order_date'] != null + ? (map['order_date'] is DateTime + ? (map['order_date'] as DateTime) + : DateTime.parse(map['order_date'].toString())) + : null, + shipperId: map['shipper_id'] as int?); + } + + static Map toMap(_Order? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'customer': CustomerSerializer.toMap(model.customer), + 'employee_id': model.employeeId, + 'order_date': model.orderDate?.toIso8601String(), + 'shipper_id': model.shipperId + }; + } +} + +abstract class OrderFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + customer, + employeeId, + orderDate, + shipperId, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String customer = 'customer'; + + static const String employeeId = 'employee_id'; + + static const String orderDate = 'order_date'; + + static const String shipperId = 'shipper_id'; +} + +const CustomerSerializer customerSerializer = CustomerSerializer(); + +class CustomerEncoder extends Converter { + const CustomerEncoder(); + + @override + Map convert(Customer model) => CustomerSerializer.toMap(model); +} + +class CustomerDecoder extends Converter { + const CustomerDecoder(); + + @override + Customer convert(Map map) => CustomerSerializer.fromMap(map); +} + +class CustomerSerializer extends Codec { + const CustomerSerializer(); + + @override + CustomerEncoder get encoder => const CustomerEncoder(); + + @override + CustomerDecoder get decoder => const CustomerDecoder(); + + static Customer fromMap(Map map) { + return Customer( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null); + } + + static Map toMap(_Customer? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String() + }; + } +} + +abstract class CustomerFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/person.dart b/packages/orm/angel_orm_mysql/test/models/person.dart new file mode 100644 index 000000000..0f7a5ec4f --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/person.dart @@ -0,0 +1,26 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; +part 'person.g.dart'; + +@serializable +@Orm(tableName: 'persons') +class _Person extends Model { + String? name; + int? age; +} + +@serializable +@Orm(tableName: 'persons', generateMigrations: false) +class _PersonWithLastOrder { + String? name; + + @Column(expression: 'po.name') + String? lastOrderName; + + @Column(expression: 'po.price') + double? lastOrderPrice; +} diff --git a/packages/orm/angel_orm_mysql/test/models/person.g.dart b/packages/orm/angel_orm_mysql/test/models/person.g.dart new file mode 100644 index 000000000..356ea2e94 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/person.g.dart @@ -0,0 +1,586 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class PersonMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'persons', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'name', + length: 255, + ); + table.integer('age'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('persons'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class PersonQuery extends Query { + PersonQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = PersonQueryWhere(this); + } + + @override + final PersonQueryValues values = PersonQueryValues(); + + List _selectedFields = []; + + PersonQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'persons'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'age', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonQueryWhere? get where { + return _where; + } + + @override + PersonQueryWhere newWhereClause() { + return PersonQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Person( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + age: fields.contains('age') ? (row[4] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonQueryWhere extends QueryWhere { + PersonQueryWhere(PersonQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + age = NumericSqlExpressionBuilder( + query, + 'age', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder age; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + age, + ]; + } +} + +class PersonQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + int? get age { + return (values['age'] as int?); + } + + set age(int? value) => values['age'] = value; + + void copyFrom(Person model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + age = model.age; + } +} + +class PersonWithLastOrderQuery + extends Query { + PersonWithLastOrderQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['last_order_name'] = 'po.name'; + expressions['last_order_price'] = 'po.price'; + _where = PersonWithLastOrderQueryWhere(this); + } + + @override + final PersonWithLastOrderQueryValues values = + PersonWithLastOrderQueryValues(); + + List _selectedFields = []; + + PersonWithLastOrderQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'persons'; + } + + @override + List get fields { + const fields = [ + 'name', + 'last_order_name', + 'last_order_price', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonWithLastOrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonWithLastOrderQueryWhere? get where { + return _where; + } + + @override + PersonWithLastOrderQueryWhere newWhereClause() { + return PersonWithLastOrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = PersonWithLastOrder( + name: fields.contains('name') ? (row[0] as String?) : null, + lastOrderName: + fields.contains('last_order_name') ? (row[1] as String?) : null, + lastOrderPrice: + fields.contains('last_order_price') ? mapToDouble(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonWithLastOrderQueryWhere extends QueryWhere { + PersonWithLastOrderQueryWhere(PersonWithLastOrderQuery query) + : name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [name]; + } +} + +class PersonWithLastOrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(PersonWithLastOrder model) { + name = model.name; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Person extends _Person { + Person({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + this.age, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + int? age; + + Person copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + int? age, + }) { + return Person( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + age: age ?? this.age); + } + + @override + bool operator ==(other) { + return other is _Person && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + other.age == age; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + age, + ]); + } + + @override + String toString() { + return 'Person(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, age=$age)'; + } + + Map toJson() { + return PersonSerializer.toMap(this); + } +} + +@generatedSerializable +class PersonWithLastOrder extends _PersonWithLastOrder { + PersonWithLastOrder({ + this.name, + this.lastOrderName, + this.lastOrderPrice, + }); + + @override + String? name; + + @override + String? lastOrderName; + + @override + double? lastOrderPrice; + + PersonWithLastOrder copyWith({ + String? name, + String? lastOrderName, + double? lastOrderPrice, + }) { + return PersonWithLastOrder( + name: name ?? this.name, + lastOrderName: lastOrderName ?? this.lastOrderName, + lastOrderPrice: lastOrderPrice ?? this.lastOrderPrice); + } + + @override + bool operator ==(other) { + return other is _PersonWithLastOrder && + other.name == name && + other.lastOrderName == lastOrderName && + other.lastOrderPrice == lastOrderPrice; + } + + @override + int get hashCode { + return hashObjects([ + name, + lastOrderName, + lastOrderPrice, + ]); + } + + @override + String toString() { + return 'PersonWithLastOrder(name=$name, lastOrderName=$lastOrderName, lastOrderPrice=$lastOrderPrice)'; + } + + Map toJson() { + return PersonWithLastOrderSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const PersonSerializer personSerializer = PersonSerializer(); + +class PersonEncoder extends Converter { + const PersonEncoder(); + + @override + Map convert(Person model) => PersonSerializer.toMap(model); +} + +class PersonDecoder extends Converter { + const PersonDecoder(); + + @override + Person convert(Map map) => PersonSerializer.fromMap(map); +} + +class PersonSerializer extends Codec { + const PersonSerializer(); + + @override + PersonEncoder get encoder => const PersonEncoder(); + + @override + PersonDecoder get decoder => const PersonDecoder(); + + static Person fromMap(Map map) { + return Person( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + age: map['age'] as int?); + } + + static Map toMap(_Person? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'age': model.age + }; + } +} + +abstract class PersonFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + age, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String age = 'age'; +} + +const PersonWithLastOrderSerializer personWithLastOrderSerializer = + PersonWithLastOrderSerializer(); + +class PersonWithLastOrderEncoder extends Converter { + const PersonWithLastOrderEncoder(); + + @override + Map convert(PersonWithLastOrder model) => + PersonWithLastOrderSerializer.toMap(model); +} + +class PersonWithLastOrderDecoder extends Converter { + const PersonWithLastOrderDecoder(); + + @override + PersonWithLastOrder convert(Map map) => + PersonWithLastOrderSerializer.fromMap(map); +} + +class PersonWithLastOrderSerializer extends Codec { + const PersonWithLastOrderSerializer(); + + @override + PersonWithLastOrderEncoder get encoder => const PersonWithLastOrderEncoder(); + + @override + PersonWithLastOrderDecoder get decoder => const PersonWithLastOrderDecoder(); + + static PersonWithLastOrder fromMap(Map map) { + return PersonWithLastOrder( + name: map['name'] as String?, + lastOrderName: map['last_order_name'] as String?, + lastOrderPrice: map['last_order_price'] as double?); + } + + static Map toMap(_PersonWithLastOrder? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'name': model.name, + 'last_order_name': model.lastOrderName, + 'last_order_price': model.lastOrderPrice + }; + } +} + +abstract class PersonWithLastOrderFields { + static const List allFields = [ + name, + lastOrderName, + lastOrderPrice, + ]; + + static const String name = 'name'; + + static const String lastOrderName = 'last_order_name'; + + static const String lastOrderPrice = 'last_order_price'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/person_order.dart b/packages/orm/angel_orm_mysql/test/models/person_order.dart new file mode 100644 index 000000000..6794d8628 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/person_order.dart @@ -0,0 +1,36 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'person_order.g.dart'; + +@orm +@serializable +abstract class _PersonOrder extends Model { + int? get personId; + + String? get name; + + double? get price; + + bool? get deleted; +} + +@serializable +@Orm(tableName: 'person_orders', generateMigrations: false) +abstract class _OrderWithPersonInfo extends Model { + String? get name; + + double? get price; + + bool? get deleted; + + @Column(expression: 'p.name') + String? get personName; + + @Column(expression: 'p.age') + int? get personAge; +} diff --git a/packages/orm/angel_orm_mysql/test/models/person_order.g.dart b/packages/orm/angel_orm_mysql/test/models/person_order.g.dart new file mode 100644 index 000000000..0d0e8ac3d --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/person_order.g.dart @@ -0,0 +1,803 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person_order.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class PersonOrderMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'person_orders', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('person_id'); + table.varChar( + 'name', + length: 255, + ); + table.double('price'); + table.boolean('deleted'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('person_orders'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class PersonOrderQuery extends Query { + PersonOrderQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = PersonOrderQueryWhere(this); + } + + @override + final PersonOrderQueryValues values = PersonOrderQueryValues(); + + List _selectedFields = []; + + PersonOrderQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'person_orders'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'person_id', + 'name', + 'price', + 'deleted', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonOrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonOrderQueryWhere? get where { + return _where; + } + + @override + PersonOrderQueryWhere newWhereClause() { + return PersonOrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = PersonOrder( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + personId: fields.contains('person_id') ? (row[3] as int?) : null, + name: fields.contains('name') ? (row[4] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[5]) : null, + deleted: fields.contains('deleted') ? mapToBool(row[6]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonOrderQueryWhere extends QueryWhere { + PersonOrderQueryWhere(PersonOrderQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + personId = NumericSqlExpressionBuilder( + query, + 'person_id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + deleted = BooleanSqlExpressionBuilder( + query, + 'deleted', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder personId; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + final BooleanSqlExpressionBuilder deleted; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]; + } +} + +class PersonOrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get personId { + return (values['person_id'] as int?); + } + + set personId(int? value) => values['person_id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + bool? get deleted { + return (values['deleted'] as bool?); + } + + set deleted(bool? value) => values['deleted'] = value; + + void copyFrom(PersonOrder model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + personId = model.personId; + name = model.name; + price = model.price; + deleted = model.deleted; + } +} + +class OrderWithPersonInfoQuery + extends Query { + OrderWithPersonInfoQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['person_name'] = 'p.name'; + expressions['person_age'] = 'p.age'; + _where = OrderWithPersonInfoQueryWhere(this); + } + + @override + final OrderWithPersonInfoQueryValues values = + OrderWithPersonInfoQueryValues(); + + List _selectedFields = []; + + OrderWithPersonInfoQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'person_orders'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'price', + 'deleted', + 'person_name', + 'person_age', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + OrderWithPersonInfoQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + OrderWithPersonInfoQueryWhere? get where { + return _where; + } + + @override + OrderWithPersonInfoQueryWhere newWhereClause() { + return OrderWithPersonInfoQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = OrderWithPersonInfo( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[4]) : null, + deleted: fields.contains('deleted') ? mapToBool(row[5]) : null, + personName: fields.contains('person_name') ? (row[6] as String?) : null, + personAge: fields.contains('person_age') ? (row[7] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class OrderWithPersonInfoQueryWhere extends QueryWhere { + OrderWithPersonInfoQueryWhere(OrderWithPersonInfoQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + deleted = BooleanSqlExpressionBuilder( + query, + 'deleted', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + final BooleanSqlExpressionBuilder deleted; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + price, + deleted, + ]; + } +} + +class OrderWithPersonInfoQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + bool? get deleted { + return (values['deleted'] as bool?); + } + + set deleted(bool? value) => values['deleted'] = value; + + void copyFrom(OrderWithPersonInfo model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + price = model.price; + deleted = model.deleted; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class PersonOrder extends _PersonOrder { + PersonOrder({ + this.id, + this.createdAt, + this.updatedAt, + this.personId, + this.name, + this.price, + this.deleted, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? personId; + + @override + String? name; + + @override + double? price; + + @override + bool? deleted; + + PersonOrder copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? personId, + String? name, + double? price, + bool? deleted, + }) { + return PersonOrder( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + personId: personId ?? this.personId, + name: name ?? this.name, + price: price ?? this.price, + deleted: deleted ?? this.deleted); + } + + @override + bool operator ==(other) { + return other is _PersonOrder && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.personId == personId && + other.name == name && + other.price == price && + other.deleted == deleted; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]); + } + + @override + String toString() { + return 'PersonOrder(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, personId=$personId, name=$name, price=$price, deleted=$deleted)'; + } + + Map toJson() { + return PersonOrderSerializer.toMap(this); + } +} + +@generatedSerializable +class OrderWithPersonInfo extends _OrderWithPersonInfo { + OrderWithPersonInfo({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + this.price, + this.deleted, + this.personName, + this.personAge, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + double? price; + + @override + bool? deleted; + + @override + String? personName; + + @override + int? personAge; + + OrderWithPersonInfo copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + double? price, + bool? deleted, + String? personName, + int? personAge, + }) { + return OrderWithPersonInfo( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + price: price ?? this.price, + deleted: deleted ?? this.deleted, + personName: personName ?? this.personName, + personAge: personAge ?? this.personAge); + } + + @override + bool operator ==(other) { + return other is _OrderWithPersonInfo && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + other.price == price && + other.deleted == deleted && + other.personName == personName && + other.personAge == personAge; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + price, + deleted, + personName, + personAge, + ]); + } + + @override + String toString() { + return 'OrderWithPersonInfo(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, price=$price, deleted=$deleted, personName=$personName, personAge=$personAge)'; + } + + Map toJson() { + return OrderWithPersonInfoSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const PersonOrderSerializer personOrderSerializer = PersonOrderSerializer(); + +class PersonOrderEncoder extends Converter { + const PersonOrderEncoder(); + + @override + Map convert(PersonOrder model) => PersonOrderSerializer.toMap(model); +} + +class PersonOrderDecoder extends Converter { + const PersonOrderDecoder(); + + @override + PersonOrder convert(Map map) => PersonOrderSerializer.fromMap(map); +} + +class PersonOrderSerializer extends Codec { + const PersonOrderSerializer(); + + @override + PersonOrderEncoder get encoder => const PersonOrderEncoder(); + + @override + PersonOrderDecoder get decoder => const PersonOrderDecoder(); + + static PersonOrder fromMap(Map map) { + return PersonOrder( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + personId: map['person_id'] as int?, + name: map['name'] as String?, + price: map['price'] as double?, + deleted: map['deleted'] as bool?); + } + + static Map toMap(_PersonOrder? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'person_id': model.personId, + 'name': model.name, + 'price': model.price, + 'deleted': model.deleted + }; + } +} + +abstract class PersonOrderFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String personId = 'person_id'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String deleted = 'deleted'; +} + +const OrderWithPersonInfoSerializer orderWithPersonInfoSerializer = + OrderWithPersonInfoSerializer(); + +class OrderWithPersonInfoEncoder extends Converter { + const OrderWithPersonInfoEncoder(); + + @override + Map convert(OrderWithPersonInfo model) => + OrderWithPersonInfoSerializer.toMap(model); +} + +class OrderWithPersonInfoDecoder extends Converter { + const OrderWithPersonInfoDecoder(); + + @override + OrderWithPersonInfo convert(Map map) => + OrderWithPersonInfoSerializer.fromMap(map); +} + +class OrderWithPersonInfoSerializer extends Codec { + const OrderWithPersonInfoSerializer(); + + @override + OrderWithPersonInfoEncoder get encoder => const OrderWithPersonInfoEncoder(); + + @override + OrderWithPersonInfoDecoder get decoder => const OrderWithPersonInfoDecoder(); + + static OrderWithPersonInfo fromMap(Map map) { + return OrderWithPersonInfo( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + price: map['price'] as double?, + deleted: map['deleted'] as bool?, + personName: map['person_name'] as String?, + personAge: map['person_age'] as int?); + } + + static Map toMap(_OrderWithPersonInfo? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'price': model.price, + 'deleted': model.deleted, + 'person_name': model.personName, + 'person_age': model.personAge + }; + } +} + +abstract class OrderWithPersonInfoFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + price, + deleted, + personName, + personAge, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String deleted = 'deleted'; + + static const String personName = 'person_name'; + + static const String personAge = 'person_age'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/quotation.dart b/packages/orm/angel_orm_mysql/test/models/quotation.dart new file mode 100644 index 000000000..02b721969 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/quotation.dart @@ -0,0 +1,17 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'quotation.g.dart'; + +@serializable +@orm +abstract class _Quotation { + @PrimaryKey(columnType: ColumnType.varChar) + String? get id; + + String? get name; + + double? get price; +} diff --git a/packages/orm/angel_orm_mysql/test/models/quotation.g.dart b/packages/orm/angel_orm_mysql/test/models/quotation.g.dart new file mode 100644 index 000000000..fa937e606 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/quotation.g.dart @@ -0,0 +1,287 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quotation.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class QuotationMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'quotations', + (table) { + table + .varChar( + 'id', + length: 255, + ) + .primaryKey(); + table.varChar( + 'name', + length: 255, + ); + table.double('price'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('quotations'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class QuotationQuery extends Query { + QuotationQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = QuotationQueryWhere(this); + } + + @override + final QuotationQueryValues values = QuotationQueryValues(); + + List _selectedFields = []; + + QuotationQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'quotations'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'name', + 'price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + QuotationQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + QuotationQueryWhere? get where { + return _where; + } + + @override + QuotationQueryWhere newWhereClause() { + return QuotationQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Quotation( + id: fields.contains('id') ? (row[0] as String?) : null, + name: fields.contains('name') ? (row[1] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class QuotationQueryWhere extends QueryWhere { + QuotationQueryWhere(QuotationQuery query) + : id = StringSqlExpressionBuilder( + query, + 'id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final StringSqlExpressionBuilder id; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + name, + price, + ]; + } +} + +class QuotationQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + void copyFrom(Quotation model) { + id = model.id; + name = model.name; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Quotation implements _Quotation { + Quotation({ + this.id, + this.name, + this.price, + }); + + @override + String? id; + + @override + String? name; + + @override + double? price; + + Quotation copyWith({ + String? id, + String? name, + double? price, + }) { + return Quotation( + id: id ?? this.id, name: name ?? this.name, price: price ?? this.price); + } + + @override + bool operator ==(other) { + return other is _Quotation && + other.id == id && + other.name == name && + other.price == price; + } + + @override + int get hashCode { + return hashObjects([ + id, + name, + price, + ]); + } + + @override + String toString() { + return 'Quotation(id=$id, name=$name, price=$price)'; + } + + Map toJson() { + return QuotationSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const QuotationSerializer quotationSerializer = QuotationSerializer(); + +class QuotationEncoder extends Converter { + const QuotationEncoder(); + + @override + Map convert(Quotation model) => QuotationSerializer.toMap(model); +} + +class QuotationDecoder extends Converter { + const QuotationDecoder(); + + @override + Quotation convert(Map map) => QuotationSerializer.fromMap(map); +} + +class QuotationSerializer extends Codec { + const QuotationSerializer(); + + @override + QuotationEncoder get encoder => const QuotationEncoder(); + + @override + QuotationDecoder get decoder => const QuotationDecoder(); + + static Quotation fromMap(Map map) { + return Quotation( + id: map['id'] as String?, + name: map['name'] as String?, + price: map['price'] as double?); + } + + static Map toMap(_Quotation? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'name': model.name, 'price': model.price}; + } +} + +abstract class QuotationFields { + static const List allFields = [ + id, + name, + price, + ]; + + static const String id = 'id'; + + static const String name = 'name'; + + static const String price = 'price'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/tree.dart b/packages/orm/angel_orm_mysql/test/models/tree.dart new file mode 100644 index 000000000..dee7eb0d9 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/tree.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'tree.g.dart'; + +@serializable +@orm +class _Tree extends Model { + @Column(indexType: IndexType.unique, type: ColumnType.smallInt) + int? rings; + + @hasMany + List<_Fruit> fruits = []; +} + +@serializable +@orm +class _Fruit extends Model { + int? treeId; + String? commonName; +} diff --git a/packages/orm/angel_orm_mysql/test/models/tree.g.dart b/packages/orm/angel_orm_mysql/test/models/tree.g.dart new file mode 100644 index 000000000..d3b568876 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/tree.g.dart @@ -0,0 +1,770 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tree.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class TreeMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'trees', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('rings').unique(); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'trees', + cascade: true, + ); + } +} + +class FruitMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'fruits', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('tree_id'); + table.varChar( + 'common_name', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('fruits'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class TreeQuery extends Query { + TreeQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = TreeQueryWhere(this); + leftJoin( + _fruits = FruitQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'tree_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'tree_id', + 'common_name', + ], + trampoline: trampoline, + ); + } + + @override + final TreeQueryValues values = TreeQueryValues(); + + List _selectedFields = []; + + TreeQueryWhere? _where; + + late FruitQuery _fruits; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'trees'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'rings', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + TreeQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + TreeQueryWhere? get where { + return _where; + } + + @override + TreeQueryWhere newWhereClause() { + return TreeQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Tree( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + rings: fields.contains('rings') ? (row[3] as int?) : null, + ); + if (row.length > 4) { + var modelOpt = FruitQuery().parseRow(row.skip(4).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(fruits: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + FruitQuery get fruits { + return _fruits; + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } +} + +class TreeQueryWhere extends QueryWhere { + TreeQueryWhere(TreeQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + rings = NumericSqlExpressionBuilder( + query, + 'rings', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder rings; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + rings, + ]; + } +} + +class TreeQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get rings { + return (values['rings'] as int?); + } + + set rings(int? value) => values['rings'] = value; + + void copyFrom(Tree model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + rings = model.rings; + } +} + +class FruitQuery extends Query { + FruitQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FruitQueryWhere(this); + } + + @override + final FruitQueryValues values = FruitQueryValues(); + + List _selectedFields = []; + + FruitQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'fruits'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'tree_id', + 'common_name', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FruitQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FruitQueryWhere? get where { + return _where; + } + + @override + FruitQueryWhere newWhereClause() { + return FruitQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Fruit( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + treeId: fields.contains('tree_id') ? (row[3] as int?) : null, + commonName: fields.contains('common_name') ? (row[4] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FruitQueryWhere extends QueryWhere { + FruitQueryWhere(FruitQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + treeId = NumericSqlExpressionBuilder( + query, + 'tree_id', + ), + commonName = StringSqlExpressionBuilder( + query, + 'common_name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder treeId; + + final StringSqlExpressionBuilder commonName; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]; + } +} + +class FruitQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get treeId { + return (values['tree_id'] as int?); + } + + set treeId(int? value) => values['tree_id'] = value; + + String? get commonName { + return (values['common_name'] as String?); + } + + set commonName(String? value) => values['common_name'] = value; + + void copyFrom(Fruit model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + treeId = model.treeId; + commonName = model.commonName; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Tree extends _Tree { + Tree({ + this.id, + this.createdAt, + this.updatedAt, + this.rings, + List<_Fruit> fruits = const [], + }) : fruits = List.unmodifiable(fruits); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? rings; + + @override + List<_Fruit> fruits; + + Tree copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? rings, + List<_Fruit>? fruits, + }) { + return Tree( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + rings: rings ?? this.rings, + fruits: fruits ?? this.fruits); + } + + @override + bool operator ==(other) { + return other is _Tree && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.rings == rings && + ListEquality<_Fruit>(DefaultEquality<_Fruit>()) + .equals(other.fruits, fruits); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + rings, + fruits, + ]); + } + + @override + String toString() { + return 'Tree(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, rings=$rings, fruits=$fruits)'; + } + + Map toJson() { + return TreeSerializer.toMap(this); + } +} + +@generatedSerializable +class Fruit extends _Fruit { + Fruit({ + this.id, + this.createdAt, + this.updatedAt, + this.treeId, + this.commonName, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? treeId; + + @override + String? commonName; + + Fruit copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? treeId, + String? commonName, + }) { + return Fruit( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + treeId: treeId ?? this.treeId, + commonName: commonName ?? this.commonName); + } + + @override + bool operator ==(other) { + return other is _Fruit && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.treeId == treeId && + other.commonName == commonName; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]); + } + + @override + String toString() { + return 'Fruit(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, treeId=$treeId, commonName=$commonName)'; + } + + Map toJson() { + return FruitSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const TreeSerializer treeSerializer = TreeSerializer(); + +class TreeEncoder extends Converter { + const TreeEncoder(); + + @override + Map convert(Tree model) => TreeSerializer.toMap(model); +} + +class TreeDecoder extends Converter { + const TreeDecoder(); + + @override + Tree convert(Map map) => TreeSerializer.fromMap(map); +} + +class TreeSerializer extends Codec { + const TreeSerializer(); + + @override + TreeEncoder get encoder => const TreeEncoder(); + + @override + TreeDecoder get decoder => const TreeDecoder(); + + static Tree fromMap(Map map) { + return Tree( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + rings: map['rings'] as int?, + fruits: map['fruits'] is Iterable + ? List.unmodifiable(((map['fruits'] as Iterable).whereType()) + .map(FruitSerializer.fromMap)) + : []); + } + + static Map toMap(_Tree? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'rings': model.rings, + 'fruits': model.fruits.map((m) => FruitSerializer.toMap(m)).toList() + }; + } +} + +abstract class TreeFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + rings, + fruits, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String rings = 'rings'; + + static const String fruits = 'fruits'; +} + +const FruitSerializer fruitSerializer = FruitSerializer(); + +class FruitEncoder extends Converter { + const FruitEncoder(); + + @override + Map convert(Fruit model) => FruitSerializer.toMap(model); +} + +class FruitDecoder extends Converter { + const FruitDecoder(); + + @override + Fruit convert(Map map) => FruitSerializer.fromMap(map); +} + +class FruitSerializer extends Codec { + const FruitSerializer(); + + @override + FruitEncoder get encoder => const FruitEncoder(); + + @override + FruitDecoder get decoder => const FruitDecoder(); + + static Fruit fromMap(Map map) { + return Fruit( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + treeId: map['tree_id'] as int?, + commonName: map['common_name'] as String?); + } + + static Map toMap(_Fruit? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'tree_id': model.treeId, + 'common_name': model.commonName + }; + } +} + +abstract class FruitFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String treeId = 'tree_id'; + + static const String commonName = 'common_name'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/unorthodox.dart b/packages/orm/angel_orm_mysql/test/models/unorthodox.dart new file mode 100644 index 000000000..3cc79bf33 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/unorthodox.dart @@ -0,0 +1,72 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'unorthodox.g.dart'; + +@serializable +@orm +abstract class _Unorthodox { + @Column(indexType: IndexType.primaryKey) + String? get name; +} + +@serializable +@orm +abstract class _WeirdJoin { + @primaryKey + int? get id; + + @BelongsTo(localKey: 'join_name', foreignKey: 'name') + _Unorthodox? get unorthodox; + + @hasOne + _Song? get song; + + @HasMany(foreignKey: 'parent') + List<_Numba> get numbas; + + @ManyToMany(_FooPivot) + List<_Foo> get foos; +} + +@serializable +@orm +abstract class _Song extends Model { + int? get weirdJoinId; + + String? get title; +} + +@serializable +@orm +class _Numba implements Comparable<_Numba> { + @primaryKey + int? i; + + int? parent; + + @override + int compareTo(_Numba other) => i!.compareTo(other.i!); +} + +@serializable +@orm +abstract class _Foo { + @primaryKey + String? get bar; + + @ManyToMany(_FooPivot) + List<_WeirdJoin> get weirdJoins; +} + +@serializable +@orm +abstract class _FooPivot { + @belongsTo + _WeirdJoin? get weirdJoin; + + @belongsTo + _Foo? get foo; +} diff --git a/packages/orm/angel_orm_mysql/test/models/unorthodox.g.dart b/packages/orm/angel_orm_mysql/test/models/unorthodox.g.dart new file mode 100644 index 000000000..a4e51c940 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/unorthodox.g.dart @@ -0,0 +1,1834 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'unorthodox.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class UnorthodoxMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'unorthodoxes', + (table) { + table + .varChar( + 'name', + length: 255, + ) + .primaryKey(); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('unorthodoxes'); + } +} + +class WeirdJoinMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'weird_joins', + (table) { + table.integer('id').primaryKey(); + table + .declare( + 'join_name', + ColumnType('varchar'), + ) + .references( + 'unorthodoxes', + 'name', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'weird_joins', + cascade: true, + ); + } +} + +class SongMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'songs', + (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('weird_join_id'); + table.varChar( + 'title', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('songs'); + } +} + +class NumbaMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'numbas', + (table) { + table.integer('i').primaryKey(); + table.integer('parent'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('numbas'); + } +} + +class FooMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'foos', + (table) { + table + .varChar( + 'bar', + length: 255, + ) + .primaryKey(); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'foos', + cascade: true, + ); + } +} + +class FooPivotMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'foo_pivots', + (table) { + table + .declare( + 'weird_join_id', + ColumnType('int'), + ) + .references( + 'weird_joins', + 'id', + ); + table + .declare( + 'foo_bar', + ColumnType('varchar'), + ) + .references( + 'foos', + 'bar', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('foo_pivots'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class UnorthodoxQuery extends Query { + UnorthodoxQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UnorthodoxQueryWhere(this); + } + + @override + final UnorthodoxQueryValues values = UnorthodoxQueryValues(); + + List _selectedFields = []; + + UnorthodoxQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'unorthodoxes'; + } + + @override + List get fields { + const _fields = ['name']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UnorthodoxQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UnorthodoxQueryWhere? get where { + return _where; + } + + @override + UnorthodoxQueryWhere newWhereClause() { + return UnorthodoxQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = + Unorthodox(name: fields.contains('name') ? (row[0] as String?) : null); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class UnorthodoxQueryWhere extends QueryWhere { + UnorthodoxQueryWhere(UnorthodoxQuery query) + : name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [name]; + } +} + +class UnorthodoxQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Unorthodox model) { + name = model.name; + } +} + +class WeirdJoinQuery extends Query { + WeirdJoinQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = WeirdJoinQueryWhere(this); + leftJoin( + _unorthodox = UnorthodoxQuery( + trampoline: trampoline, + parent: this, + ), + 'join_name', + 'name', + additionalFields: const ['name'], + trampoline: trampoline, + ); + leftJoin( + _song = SongQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'weird_join_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'weird_join_id', + 'title', + ], + trampoline: trampoline, + ); + leftJoin( + _numbas = NumbaQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'parent', + additionalFields: const [ + 'i', + 'parent', + ], + trampoline: trampoline, + ); + leftJoin( + '(SELECT foo_pivots.weird_join_id, foos.bar FROM foos LEFT JOIN foo_pivots ON foo_pivots.foo_bar=foos.bar)', + 'id', + 'weird_join_id', + additionalFields: const ['bar'], + trampoline: trampoline, + ); + } + + @override + final WeirdJoinQueryValues values = WeirdJoinQueryValues(); + + List _selectedFields = []; + + WeirdJoinQueryWhere? _where; + + late UnorthodoxQuery _unorthodox; + + late SongQuery _song; + + late NumbaQuery _numbas; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'weird_joins'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'join_name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + WeirdJoinQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + WeirdJoinQueryWhere? get where { + return _where; + } + + @override + WeirdJoinQueryWhere newWhereClause() { + return WeirdJoinQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = WeirdJoin(id: fields.contains('id') ? (row[0] as int?) : null); + if (row.length > 2) { + var modelOpt = UnorthodoxQuery().parseRow(row.skip(2).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(unorthodox: m); + }); + } + if (row.length > 3) { + var modelOpt = SongQuery().parseRow(row.skip(3).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(song: m); + }); + } + if (row.length > 8) { + var modelOpt = NumbaQuery().parseRow(row.skip(8).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(numbas: [m]); + }); + } + if (row.length > 10) { + var modelOpt = FooQuery().parseRow(row.skip(10).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foos: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + UnorthodoxQuery get unorthodox { + return _unorthodox; + } + + SongQuery get song { + return _song; + } + + NumbaQuery get numbas { + return _numbas; + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('weird_joins') && + trampoline.contains('foo_pivots'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } +} + +class WeirdJoinQueryWhere extends QueryWhere { + WeirdJoinQueryWhere(WeirdJoinQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + joinName = StringSqlExpressionBuilder( + query, + 'join_name', + ); + + final NumericSqlExpressionBuilder id; + + final StringSqlExpressionBuilder joinName; + + @override + List get expressionBuilders { + return [ + id, + joinName, + ]; + } +} + +class WeirdJoinQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get id { + return (values['id'] as int?); + } + + set id(int? value) => values['id'] = value; + + String? get joinName { + return (values['join_name'] as String?); + } + + set joinName(String? value) => values['join_name'] = value; + + void copyFrom(WeirdJoin model) { + id = model.id; + if (model.unorthodox != null) { + values['join_name'] = model.unorthodox?.name; + } + } +} + +class SongQuery extends Query { + SongQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = SongQueryWhere(this); + } + + @override + final SongQueryValues values = SongQueryValues(); + + List _selectedFields = []; + + SongQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'songs'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'weird_join_id', + 'title', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + SongQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + SongQueryWhere? get where { + return _where; + } + + @override + SongQueryWhere newWhereClause() { + return SongQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Song( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + weirdJoinId: fields.contains('weird_join_id') ? (row[3] as int?) : null, + title: fields.contains('title') ? (row[4] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class SongQueryWhere extends QueryWhere { + SongQueryWhere(SongQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + weirdJoinId = NumericSqlExpressionBuilder( + query, + 'weird_join_id', + ), + title = StringSqlExpressionBuilder( + query, + 'title', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder weirdJoinId; + + final StringSqlExpressionBuilder title; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]; + } +} + +class SongQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get weirdJoinId { + return (values['weird_join_id'] as int?); + } + + set weirdJoinId(int? value) => values['weird_join_id'] = value; + + String? get title { + return (values['title'] as String?); + } + + set title(String? value) => values['title'] = value; + + void copyFrom(Song model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + weirdJoinId = model.weirdJoinId; + title = model.title; + } +} + +class NumbaQuery extends Query { + NumbaQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = NumbaQueryWhere(this); + } + + @override + final NumbaQueryValues values = NumbaQueryValues(); + + List _selectedFields = []; + + NumbaQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'numbas'; + } + + @override + List get fields { + const _fields = [ + 'i', + 'parent', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + NumbaQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + NumbaQueryWhere? get where { + return _where; + } + + @override + NumbaQueryWhere newWhereClause() { + return NumbaQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Numba( + i: fields.contains('i') ? (row[0] as int?) : null, + parent: fields.contains('parent') ? (row[1] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class NumbaQueryWhere extends QueryWhere { + NumbaQueryWhere(NumbaQuery query) + : i = NumericSqlExpressionBuilder( + query, + 'i', + ), + parent = NumericSqlExpressionBuilder( + query, + 'parent', + ); + + final NumericSqlExpressionBuilder i; + + final NumericSqlExpressionBuilder parent; + + @override + List get expressionBuilders { + return [ + i, + parent, + ]; + } +} + +class NumbaQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get i { + return (values['i'] as int?); + } + + set i(int? value) => values['i'] = value; + + int? get parent { + return (values['parent'] as int?); + } + + set parent(int? value) => values['parent'] = value; + + void copyFrom(Numba model) { + i = model.i; + parent = model.parent; + } +} + +class FooQuery extends Query { + FooQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FooQueryWhere(this); + leftJoin( + '(SELECT foo_pivots.foo_bar, weird_joins.id, weird_joins.join_name FROM weird_joins LEFT JOIN foo_pivots ON foo_pivots.weird_join_id=weird_joins.id)', + 'bar', + 'foo_bar', + additionalFields: const [ + 'id', + 'join_name', + ], + trampoline: trampoline, + ); + } + + @override + final FooQueryValues values = FooQueryValues(); + + List _selectedFields = []; + + FooQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'foos'; + } + + @override + List get fields { + const _fields = ['bar']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FooQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FooQueryWhere? get where { + return _where; + } + + @override + FooQueryWhere newWhereClause() { + return FooQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Foo(bar: fields.contains('bar') ? (row[0] as String?) : null); + if (row.length > 1) { + var modelOpt = WeirdJoinQuery().parseRow(row.skip(1).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(weirdJoins: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('foos') && + trampoline.contains('foo_pivots'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } +} + +class FooQueryWhere extends QueryWhere { + FooQueryWhere(FooQuery query) + : bar = StringSqlExpressionBuilder( + query, + 'bar', + ); + + final StringSqlExpressionBuilder bar; + + @override + List get expressionBuilders { + return [bar]; + } +} + +class FooQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get bar { + return (values['bar'] as String?); + } + + set bar(String? value) => values['bar'] = value; + + void copyFrom(Foo model) { + bar = model.bar; + } +} + +class FooPivotQuery extends Query { + FooPivotQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FooPivotQueryWhere(this); + leftJoin( + _weirdJoin = WeirdJoinQuery( + trampoline: trampoline, + parent: this, + ), + 'weird_join_id', + 'id', + additionalFields: const [ + 'id', + 'join_name', + ], + trampoline: trampoline, + ); + leftJoin( + _foo = FooQuery( + trampoline: trampoline, + parent: this, + ), + 'foo_bar', + 'bar', + additionalFields: const ['bar'], + trampoline: trampoline, + ); + } + + @override + final FooPivotQueryValues values = FooPivotQueryValues(); + + List _selectedFields = []; + + FooPivotQueryWhere? _where; + + late WeirdJoinQuery _weirdJoin; + + late FooQuery _foo; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'foo_pivots'; + } + + @override + List get fields { + const _fields = [ + 'weird_join_id', + 'foo_bar', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FooPivotQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FooPivotQueryWhere? get where { + return _where; + } + + @override + FooPivotQueryWhere newWhereClause() { + return FooPivotQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = FooPivot(); + if (row.length > 2) { + var modelOpt = WeirdJoinQuery().parseRow(row.skip(2).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(weirdJoin: m); + }); + } + if (row.length > 4) { + var modelOpt = FooQuery().parseRow(row.skip(4).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foo: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + WeirdJoinQuery get weirdJoin { + return _weirdJoin; + } + + FooQuery get foo { + return _foo; + } +} + +class FooPivotQueryWhere extends QueryWhere { + FooPivotQueryWhere(FooPivotQuery query) + : weirdJoinId = NumericSqlExpressionBuilder( + query, + 'weird_join_id', + ), + fooBar = StringSqlExpressionBuilder( + query, + 'foo_bar', + ); + + final NumericSqlExpressionBuilder weirdJoinId; + + final StringSqlExpressionBuilder fooBar; + + @override + List get expressionBuilders { + return [ + weirdJoinId, + fooBar, + ]; + } +} + +class FooPivotQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get weirdJoinId { + return (values['weird_join_id'] as int?); + } + + set weirdJoinId(int? value) => values['weird_join_id'] = value; + + String? get fooBar { + return (values['foo_bar'] as String?); + } + + set fooBar(String? value) => values['foo_bar'] = value; + + void copyFrom(FooPivot model) { + if (model.weirdJoin != null) { + values['weird_join_id'] = model.weirdJoin?.id; + } + if (model.foo != null) { + values['foo_bar'] = model.foo?.bar; + } + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Unorthodox implements _Unorthodox { + Unorthodox({this.name}); + + @override + String? name; + + Unorthodox copyWith({String? name}) { + return Unorthodox(name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Unorthodox && other.name == name; + } + + @override + int get hashCode { + return hashObjects([name]); + } + + @override + String toString() { + return 'Unorthodox(name=$name)'; + } + + Map toJson() { + return UnorthodoxSerializer.toMap(this); + } +} + +@generatedSerializable +class WeirdJoin implements _WeirdJoin { + WeirdJoin({ + this.id, + this.unorthodox, + this.song, + this.numbas = const [], + this.foos = const [], + }); + + @override + int? id; + + @override + _Unorthodox? unorthodox; + + @override + _Song? song; + + @override + List<_Numba> numbas; + + @override + List<_Foo> foos; + + WeirdJoin copyWith({ + int? id, + _Unorthodox? unorthodox, + _Song? song, + List<_Numba>? numbas, + List<_Foo>? foos, + }) { + return WeirdJoin( + id: id ?? this.id, + unorthodox: unorthodox ?? this.unorthodox, + song: song ?? this.song, + numbas: numbas ?? this.numbas, + foos: foos ?? this.foos); + } + + @override + bool operator ==(other) { + return other is _WeirdJoin && + other.id == id && + other.unorthodox == unorthodox && + other.song == song && + ListEquality<_Numba>(DefaultEquality<_Numba>()) + .equals(other.numbas, numbas) && + ListEquality<_Foo>(DefaultEquality<_Foo>()).equals(other.foos, foos); + } + + @override + int get hashCode { + return hashObjects([ + id, + unorthodox, + song, + numbas, + foos, + ]); + } + + @override + String toString() { + return 'WeirdJoin(id=$id, unorthodox=$unorthodox, song=$song, numbas=$numbas, foos=$foos)'; + } + + Map toJson() { + return WeirdJoinSerializer.toMap(this); + } +} + +@generatedSerializable +class Song extends _Song { + Song({ + this.id, + this.createdAt, + this.updatedAt, + this.weirdJoinId, + this.title, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? weirdJoinId; + + @override + String? title; + + Song copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? weirdJoinId, + String? title, + }) { + return Song( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + weirdJoinId: weirdJoinId ?? this.weirdJoinId, + title: title ?? this.title); + } + + @override + bool operator ==(other) { + return other is _Song && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.weirdJoinId == weirdJoinId && + other.title == title; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]); + } + + @override + String toString() { + return 'Song(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, weirdJoinId=$weirdJoinId, title=$title)'; + } + + Map toJson() { + return SongSerializer.toMap(this); + } +} + +@generatedSerializable +class Numba extends _Numba { + Numba({ + this.i, + this.parent, + }); + + @override + int? i; + + @override + int? parent; + + Numba copyWith({ + int? i, + int? parent, + }) { + return Numba(i: i ?? this.i, parent: parent ?? this.parent); + } + + @override + bool operator ==(other) { + return other is _Numba && other.i == i && other.parent == parent; + } + + @override + int get hashCode { + return hashObjects([ + i, + parent, + ]); + } + + @override + String toString() { + return 'Numba(i=$i, parent=$parent)'; + } + + Map toJson() { + return NumbaSerializer.toMap(this); + } +} + +@generatedSerializable +class Foo implements _Foo { + Foo({ + this.bar, + this.weirdJoins = const [], + }); + + @override + String? bar; + + @override + List<_WeirdJoin> weirdJoins; + + Foo copyWith({ + String? bar, + List<_WeirdJoin>? weirdJoins, + }) { + return Foo(bar: bar ?? this.bar, weirdJoins: weirdJoins ?? this.weirdJoins); + } + + @override + bool operator ==(other) { + return other is _Foo && + other.bar == bar && + ListEquality<_WeirdJoin>(DefaultEquality<_WeirdJoin>()) + .equals(other.weirdJoins, weirdJoins); + } + + @override + int get hashCode { + return hashObjects([ + bar, + weirdJoins, + ]); + } + + @override + String toString() { + return 'Foo(bar=$bar, weirdJoins=$weirdJoins)'; + } + + Map toJson() { + return FooSerializer.toMap(this); + } +} + +@generatedSerializable +class FooPivot implements _FooPivot { + FooPivot({ + this.weirdJoin, + this.foo, + }); + + @override + _WeirdJoin? weirdJoin; + + @override + _Foo? foo; + + FooPivot copyWith({ + _WeirdJoin? weirdJoin, + _Foo? foo, + }) { + return FooPivot( + weirdJoin: weirdJoin ?? this.weirdJoin, foo: foo ?? this.foo); + } + + @override + bool operator ==(other) { + return other is _FooPivot && + other.weirdJoin == weirdJoin && + other.foo == foo; + } + + @override + int get hashCode { + return hashObjects([ + weirdJoin, + foo, + ]); + } + + @override + String toString() { + return 'FooPivot(weirdJoin=$weirdJoin, foo=$foo)'; + } + + Map toJson() { + return FooPivotSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const UnorthodoxSerializer unorthodoxSerializer = UnorthodoxSerializer(); + +class UnorthodoxEncoder extends Converter { + const UnorthodoxEncoder(); + + @override + Map convert(Unorthodox model) => UnorthodoxSerializer.toMap(model); +} + +class UnorthodoxDecoder extends Converter { + const UnorthodoxDecoder(); + + @override + Unorthodox convert(Map map) => UnorthodoxSerializer.fromMap(map); +} + +class UnorthodoxSerializer extends Codec { + const UnorthodoxSerializer(); + + @override + UnorthodoxEncoder get encoder => const UnorthodoxEncoder(); + + @override + UnorthodoxDecoder get decoder => const UnorthodoxDecoder(); + + static Unorthodox fromMap(Map map) { + return Unorthodox(name: map['name'] as String?); + } + + static Map toMap(_Unorthodox? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'name': model.name}; + } +} + +abstract class UnorthodoxFields { + static const List allFields = [name]; + + static const String name = 'name'; +} + +const WeirdJoinSerializer weirdJoinSerializer = WeirdJoinSerializer(); + +class WeirdJoinEncoder extends Converter { + const WeirdJoinEncoder(); + + @override + Map convert(WeirdJoin model) => WeirdJoinSerializer.toMap(model); +} + +class WeirdJoinDecoder extends Converter { + const WeirdJoinDecoder(); + + @override + WeirdJoin convert(Map map) => WeirdJoinSerializer.fromMap(map); +} + +class WeirdJoinSerializer extends Codec { + const WeirdJoinSerializer(); + + @override + WeirdJoinEncoder get encoder => const WeirdJoinEncoder(); + + @override + WeirdJoinDecoder get decoder => const WeirdJoinDecoder(); + + static WeirdJoin fromMap(Map map) { + return WeirdJoin( + id: map['id'] as int?, + unorthodox: map['unorthodox'] != null + ? UnorthodoxSerializer.fromMap(map['unorthodox'] as Map) + : null, + song: map['song'] != null + ? SongSerializer.fromMap(map['song'] as Map) + : null, + numbas: map['numbas'] is Iterable + ? List.unmodifiable(((map['numbas'] as Iterable).whereType()) + .map(NumbaSerializer.fromMap)) + : [], + foos: map['foos'] is Iterable + ? List.unmodifiable(((map['foos'] as Iterable).whereType()) + .map(FooSerializer.fromMap)) + : []); + } + + static Map toMap(_WeirdJoin? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'unorthodox': UnorthodoxSerializer.toMap(model.unorthodox), + 'song': SongSerializer.toMap(model.song), + 'numbas': model.numbas.map((m) => NumbaSerializer.toMap(m)).toList(), + 'foos': model.foos.map((m) => FooSerializer.toMap(m)).toList() + }; + } +} + +abstract class WeirdJoinFields { + static const List allFields = [ + id, + unorthodox, + song, + numbas, + foos, + ]; + + static const String id = 'id'; + + static const String unorthodox = 'unorthodox'; + + static const String song = 'song'; + + static const String numbas = 'numbas'; + + static const String foos = 'foos'; +} + +const SongSerializer songSerializer = SongSerializer(); + +class SongEncoder extends Converter { + const SongEncoder(); + + @override + Map convert(Song model) => SongSerializer.toMap(model); +} + +class SongDecoder extends Converter { + const SongDecoder(); + + @override + Song convert(Map map) => SongSerializer.fromMap(map); +} + +class SongSerializer extends Codec { + const SongSerializer(); + + @override + SongEncoder get encoder => const SongEncoder(); + + @override + SongDecoder get decoder => const SongDecoder(); + + static Song fromMap(Map map) { + return Song( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + weirdJoinId: map['weird_join_id'] as int?, + title: map['title'] as String?); + } + + static Map toMap(_Song? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'weird_join_id': model.weirdJoinId, + 'title': model.title + }; + } +} + +abstract class SongFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String weirdJoinId = 'weird_join_id'; + + static const String title = 'title'; +} + +const NumbaSerializer numbaSerializer = NumbaSerializer(); + +class NumbaEncoder extends Converter { + const NumbaEncoder(); + + @override + Map convert(Numba model) => NumbaSerializer.toMap(model); +} + +class NumbaDecoder extends Converter { + const NumbaDecoder(); + + @override + Numba convert(Map map) => NumbaSerializer.fromMap(map); +} + +class NumbaSerializer extends Codec { + const NumbaSerializer(); + + @override + NumbaEncoder get encoder => const NumbaEncoder(); + + @override + NumbaDecoder get decoder => const NumbaDecoder(); + + static Numba fromMap(Map map) { + return Numba(i: map['i'] as int?, parent: map['parent'] as int?); + } + + static Map toMap(_Numba? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'i': model.i, 'parent': model.parent}; + } +} + +abstract class NumbaFields { + static const List allFields = [ + i, + parent, + ]; + + static const String i = 'i'; + + static const String parent = 'parent'; +} + +const FooSerializer fooSerializer = FooSerializer(); + +class FooEncoder extends Converter { + const FooEncoder(); + + @override + Map convert(Foo model) => FooSerializer.toMap(model); +} + +class FooDecoder extends Converter { + const FooDecoder(); + + @override + Foo convert(Map map) => FooSerializer.fromMap(map); +} + +class FooSerializer extends Codec { + const FooSerializer(); + + @override + FooEncoder get encoder => const FooEncoder(); + + @override + FooDecoder get decoder => const FooDecoder(); + + static Foo fromMap(Map map) { + return Foo( + bar: map['bar'] as String?, + weirdJoins: map['weird_joins'] is Iterable + ? List.unmodifiable( + ((map['weird_joins'] as Iterable).whereType()) + .map(WeirdJoinSerializer.fromMap)) + : []); + } + + static Map toMap(_Foo? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'bar': model.bar, + 'weird_joins': + model.weirdJoins.map((m) => WeirdJoinSerializer.toMap(m)).toList() + }; + } +} + +abstract class FooFields { + static const List allFields = [ + bar, + weirdJoins, + ]; + + static const String bar = 'bar'; + + static const String weirdJoins = 'weird_joins'; +} + +const FooPivotSerializer fooPivotSerializer = FooPivotSerializer(); + +class FooPivotEncoder extends Converter { + const FooPivotEncoder(); + + @override + Map convert(FooPivot model) => FooPivotSerializer.toMap(model); +} + +class FooPivotDecoder extends Converter { + const FooPivotDecoder(); + + @override + FooPivot convert(Map map) => FooPivotSerializer.fromMap(map); +} + +class FooPivotSerializer extends Codec { + const FooPivotSerializer(); + + @override + FooPivotEncoder get encoder => const FooPivotEncoder(); + + @override + FooPivotDecoder get decoder => const FooPivotDecoder(); + + static FooPivot fromMap(Map map) { + return FooPivot( + weirdJoin: map['weird_join'] != null + ? WeirdJoinSerializer.fromMap(map['weird_join'] as Map) + : null, + foo: map['foo'] != null + ? FooSerializer.fromMap(map['foo'] as Map) + : null); + } + + static Map toMap(_FooPivot? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'weird_join': WeirdJoinSerializer.toMap(model.weirdJoin), + 'foo': FooSerializer.toMap(model.foo) + }; + } +} + +abstract class FooPivotFields { + static const List allFields = [ + weirdJoin, + foo, + ]; + + static const String weirdJoin = 'weird_join'; + + static const String foo = 'foo'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/user.dart b/packages/orm/angel_orm_mysql/test/models/user.dart new file mode 100644 index 000000000..27195c60c --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/user.dart @@ -0,0 +1,38 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'user.g.dart'; + +@serializable +@orm +abstract class _User extends Model { + String? get username; + String? get password; + String? get email; + + @ManyToMany(_RoleUser) + List<_Role> get roles; +} + +@serializable +@orm +abstract class _RoleUser { + @belongsTo + _Role? get role; + + @belongsTo + _User? get user; +} + +@serializable +@orm +abstract class _Role extends Model { + String? name; + + @ManyToMany(_RoleUser) + List<_User> get users; +} diff --git a/packages/orm/angel_orm_mysql/test/models/user.g.dart b/packages/orm/angel_orm_mysql/test/models/user.g.dart new file mode 100644 index 000000000..1d6fcbcaa --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/user.g.dart @@ -0,0 +1,1214 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class UserMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'users', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'username', + length: 255, + ); + table.varChar( + 'password', + length: 255, + ); + table.varChar( + 'email', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'users', + cascade: true, + ); + } +} + +class RoleUserMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'role_users', + (table) { + table + .declare( + 'role_id', + ColumnType('int'), + ) + .references( + 'roles', + 'id', + ); + table + .declare( + 'user_id', + ColumnType('int'), + ) + .references( + 'users', + 'id', + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('role_users'); + } +} + +class RoleMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'roles', + (table) { + table.integer('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar( + 'name', + length: 255, + ); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop( + 'roles', + cascade: true, + ); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class UserQuery extends Query { + UserQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UserQueryWhere(this); + leftJoin( + '(SELECT role_users.user_id, roles.id, roles.created_at, roles.updated_at, roles.name FROM roles LEFT JOIN role_users ON role_users.role_id=roles.id)', + 'id', + 'user_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + } + + @override + final UserQueryValues values = UserQueryValues(); + + List _selectedFields = []; + + UserQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'users'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UserQueryWhere? get where { + return _where; + } + + @override + UserQueryWhere newWhereClause() { + return UserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = User( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + username: fields.contains('username') ? (row[3] as String?) : null, + password: fields.contains('password') ? (row[4] as String?) : null, + email: fields.contains('email') ? (row[5] as String?) : null, + ); + if (row.length > 6) { + var modelOpt = RoleQuery().parseRow(row.skip(6).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(roles: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('users') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } +} + +class UserQueryWhere extends QueryWhere { + UserQueryWhere(UserQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + username = StringSqlExpressionBuilder( + query, + 'username', + ), + password = StringSqlExpressionBuilder( + query, + 'password', + ), + email = StringSqlExpressionBuilder( + query, + 'email', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder username; + + final StringSqlExpressionBuilder password; + + final StringSqlExpressionBuilder email; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + username, + password, + email, + ]; + } +} + +class UserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get username { + return (values['username'] as String?); + } + + set username(String? value) => values['username'] = value; + + String? get password { + return (values['password'] as String?); + } + + set password(String? value) => values['password'] = value; + + String? get email { + return (values['email'] as String?); + } + + set email(String? value) => values['email'] = value; + + void copyFrom(User model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + username = model.username; + password = model.password; + email = model.email; + } +} + +class RoleUserQuery extends Query { + RoleUserQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleUserQueryWhere(this); + leftJoin( + _role = RoleQuery( + trampoline: trampoline, + parent: this, + ), + 'role_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + leftJoin( + _user = UserQuery( + trampoline: trampoline, + parent: this, + ), + 'user_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ], + trampoline: trampoline, + ); + } + + @override + final RoleUserQueryValues values = RoleUserQueryValues(); + + List _selectedFields = []; + + RoleUserQueryWhere? _where; + + late RoleQuery _role; + + late UserQuery _user; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'role_users'; + } + + @override + List get fields { + const fields = [ + 'role_id', + 'user_id', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleUserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleUserQueryWhere? get where { + return _where; + } + + @override + RoleUserQueryWhere newWhereClause() { + return RoleUserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = RoleUser(); + if (row.length > 2) { + var modelOpt = RoleQuery().parseRow(row.skip(2).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(role: m); + }); + } + if (row.length > 6) { + var modelOpt = UserQuery().parseRow(row.skip(6).take(6).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(user: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + RoleQuery get role { + return _role; + } + + UserQuery get user { + return _user; + } +} + +class RoleUserQueryWhere extends QueryWhere { + RoleUserQueryWhere(RoleUserQuery query) + : roleId = NumericSqlExpressionBuilder( + query, + 'role_id', + ), + userId = NumericSqlExpressionBuilder( + query, + 'user_id', + ); + + final NumericSqlExpressionBuilder roleId; + + final NumericSqlExpressionBuilder userId; + + @override + List get expressionBuilders { + return [ + roleId, + userId, + ]; + } +} + +class RoleUserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int get roleId { + return (values['role_id'] as int); + } + + set roleId(int value) => values['role_id'] = value; + + int get userId { + return (values['user_id'] as int); + } + + set userId(int value) => values['user_id'] = value; + + void copyFrom(RoleUser model) { + if (model.role != null) { + values['role_id'] = model.role?.id; + } + if (model.user != null) { + values['user_id'] = model.user?.id; + } + } +} + +class RoleQuery extends Query { + RoleQuery({ + super.parent, + Set? trampoline, + }) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleQueryWhere(this); + leftJoin( + '(SELECT role_users.role_id, users.id, users.created_at, users.updated_at, users.username, users.password, users.email FROM users LEFT JOIN role_users ON role_users.user_id=users.id)', + 'id', + 'role_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ], + trampoline: trampoline, + ); + } + + @override + final RoleQueryValues values = RoleQueryValues(); + + List _selectedFields = []; + + RoleQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'roles'; + } + + @override + List get fields { + const fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + ]; + return _selectedFields.isEmpty + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleQueryWhere? get where { + return _where; + } + + @override + RoleQueryWhere newWhereClause() { + return RoleQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Role( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + ); + if (row.length > 4) { + var modelOpt = UserQuery().parseRow(row.skip(4).take(6).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(users: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('roles') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } +} + +class RoleQueryWhere extends QueryWhere { + RoleQueryWhere(RoleQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + ]; + } +} + +class RoleQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Role model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class User extends _User { + User({ + this.id, + this.createdAt, + this.updatedAt, + this.username, + this.password, + this.email, + List<_Role> roles = const [], + }) : roles = List.unmodifiable(roles); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? username; + + @override + String? password; + + @override + String? email; + + @override + List<_Role> roles; + + User copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? username, + String? password, + String? email, + List<_Role>? roles, + }) { + return User( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + username: username ?? this.username, + password: password ?? this.password, + email: email ?? this.email, + roles: roles ?? this.roles); + } + + @override + bool operator ==(other) { + return other is _User && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.username == username && + other.password == password && + other.email == email && + ListEquality<_Role>(DefaultEquality<_Role>()) + .equals(other.roles, roles); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + username, + password, + email, + roles, + ]); + } + + @override + String toString() { + return 'User(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, username=$username, password=$password, email=$email, roles=$roles)'; + } + + Map toJson() { + return UserSerializer.toMap(this); + } +} + +@generatedSerializable +class RoleUser implements _RoleUser { + RoleUser({ + this.role, + this.user, + }); + + @override + _Role? role; + + @override + _User? user; + + RoleUser copyWith({ + _Role? role, + _User? user, + }) { + return RoleUser(role: role ?? this.role, user: user ?? this.user); + } + + @override + bool operator ==(other) { + return other is _RoleUser && other.role == role && other.user == user; + } + + @override + int get hashCode { + return hashObjects([ + role, + user, + ]); + } + + @override + String toString() { + return 'RoleUser(role=$role, user=$user)'; + } + + Map toJson() { + return RoleUserSerializer.toMap(this); + } +} + +@generatedSerializable +class Role extends _Role { + Role({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + List<_User> users = const [], + }) : users = List.unmodifiable(users); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + List<_User> users; + + Role copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + List<_User>? users, + }) { + return Role( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + users: users ?? this.users); + } + + @override + bool operator ==(other) { + return other is _Role && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + ListEquality<_User>(DefaultEquality<_User>()) + .equals(other.users, users); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + users, + ]); + } + + @override + String toString() { + return 'Role(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, users=$users)'; + } + + Map toJson() { + return RoleSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const UserSerializer userSerializer = UserSerializer(); + +class UserEncoder extends Converter { + const UserEncoder(); + + @override + Map convert(User model) => UserSerializer.toMap(model); +} + +class UserDecoder extends Converter { + const UserDecoder(); + + @override + User convert(Map map) => UserSerializer.fromMap(map); +} + +class UserSerializer extends Codec { + const UserSerializer(); + + @override + UserEncoder get encoder => const UserEncoder(); + + @override + UserDecoder get decoder => const UserDecoder(); + + static User fromMap(Map map) { + return User( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + username: map['username'] as String?, + password: map['password'] as String?, + email: map['email'] as String?, + roles: map['roles'] is Iterable + ? List.unmodifiable(((map['roles'] as Iterable).whereType()) + .map(RoleSerializer.fromMap)) + : []); + } + + static Map toMap(_User? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'username': model.username, + 'password': model.password, + 'email': model.email, + 'roles': model.roles.map((m) => RoleSerializer.toMap(m)).toList() + }; + } +} + +abstract class UserFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + username, + password, + email, + roles, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String username = 'username'; + + static const String password = 'password'; + + static const String email = 'email'; + + static const String roles = 'roles'; +} + +const RoleUserSerializer roleUserSerializer = RoleUserSerializer(); + +class RoleUserEncoder extends Converter { + const RoleUserEncoder(); + + @override + Map convert(RoleUser model) => RoleUserSerializer.toMap(model); +} + +class RoleUserDecoder extends Converter { + const RoleUserDecoder(); + + @override + RoleUser convert(Map map) => RoleUserSerializer.fromMap(map); +} + +class RoleUserSerializer extends Codec { + const RoleUserSerializer(); + + @override + RoleUserEncoder get encoder => const RoleUserEncoder(); + + @override + RoleUserDecoder get decoder => const RoleUserDecoder(); + + static RoleUser fromMap(Map map) { + return RoleUser( + role: map['role'] != null + ? RoleSerializer.fromMap(map['role'] as Map) + : null, + user: map['user'] != null + ? UserSerializer.fromMap(map['user'] as Map) + : null); + } + + static Map toMap(_RoleUser? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': RoleSerializer.toMap(model.role), + 'user': UserSerializer.toMap(model.user) + }; + } +} + +abstract class RoleUserFields { + static const List allFields = [ + role, + user, + ]; + + static const String role = 'role'; + + static const String user = 'user'; +} + +const RoleSerializer roleSerializer = RoleSerializer(); + +class RoleEncoder extends Converter { + const RoleEncoder(); + + @override + Map convert(Role model) => RoleSerializer.toMap(model); +} + +class RoleDecoder extends Converter { + const RoleDecoder(); + + @override + Role convert(Map map) => RoleSerializer.fromMap(map); +} + +class RoleSerializer extends Codec { + const RoleSerializer(); + + @override + RoleEncoder get encoder => const RoleEncoder(); + + @override + RoleDecoder get decoder => const RoleDecoder(); + + static Role fromMap(Map map) { + return Role( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + users: map['users'] is Iterable + ? List.unmodifiable(((map['users'] as Iterable).whereType()) + .map(UserSerializer.fromMap)) + : []); + } + + static Map toMap(_Role? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'users': model.users.map((m) => UserSerializer.toMap(m)).toList() + }; + } +} + +abstract class RoleFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + users, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String users = 'users'; +} diff --git a/packages/orm/angel_orm_mysql/test/models/world.dart b/packages/orm/angel_orm_mysql/test/models/world.dart new file mode 100644 index 000000000..313762685 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/world.dart @@ -0,0 +1,16 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:optional/optional.dart'; + +part 'world.g.dart'; + +@serializable +@Orm(tableName: 'world') +abstract class _World { + @primaryKey + int? id; + + @Column() + int? randomNumber; +} diff --git a/packages/orm/angel_orm_mysql/test/models/world.g.dart b/packages/orm/angel_orm_mysql/test/models/world.g.dart new file mode 100644 index 000000000..164d6b6f1 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/models/world.g.dart @@ -0,0 +1,250 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'world.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class WorldMigration extends Migration { + @override + void up(Schema schema) { + schema.create( + 'world', + (table) { + table.integer('id').primaryKey(); + table.integer('random_number'); + }, + ); + } + + @override + void down(Schema schema) { + schema.drop('world'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class WorldQuery extends Query { + WorldQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = WorldQueryWhere(this); + } + + @override + final WorldQueryValues values = WorldQueryValues(); + + List _selectedFields = []; + + WorldQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'world'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'random_number', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + WorldQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + WorldQueryWhere? get where { + return _where; + } + + @override + WorldQueryWhere newWhereClause() { + return WorldQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = World( + id: fields.contains('id') ? (row[0] as int?) : null, + randomNumber: fields.contains('random_number') ? (row[1] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class WorldQueryWhere extends QueryWhere { + WorldQueryWhere(WorldQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + randomNumber = NumericSqlExpressionBuilder( + query, + 'random_number', + ); + + final NumericSqlExpressionBuilder id; + + final NumericSqlExpressionBuilder randomNumber; + + @override + List get expressionBuilders { + return [ + id, + randomNumber, + ]; + } +} + +class WorldQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get id { + return (values['id'] as int?); + } + + set id(int? value) => values['id'] = value; + + int? get randomNumber { + return (values['random_number'] as int?); + } + + set randomNumber(int? value) => values['random_number'] = value; + + void copyFrom(World model) { + id = model.id; + randomNumber = model.randomNumber; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class World extends _World { + World({ + this.id, + this.randomNumber, + }); + + @override + int? id; + + @override + int? randomNumber; + + World copyWith({ + int? id, + int? randomNumber, + }) { + return World( + id: id ?? this.id, randomNumber: randomNumber ?? this.randomNumber); + } + + @override + bool operator ==(other) { + return other is _World && + other.id == id && + other.randomNumber == randomNumber; + } + + @override + int get hashCode { + return hashObjects([ + id, + randomNumber, + ]); + } + + @override + String toString() { + return 'World(id=$id, randomNumber=$randomNumber)'; + } + + Map toJson() { + return WorldSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const WorldSerializer worldSerializer = WorldSerializer(); + +class WorldEncoder extends Converter { + const WorldEncoder(); + + @override + Map convert(World model) => WorldSerializer.toMap(model); +} + +class WorldDecoder extends Converter { + const WorldDecoder(); + + @override + World convert(Map map) => WorldSerializer.fromMap(map); +} + +class WorldSerializer extends Codec { + const WorldSerializer(); + + @override + WorldEncoder get encoder => const WorldEncoder(); + + @override + WorldDecoder get decoder => const WorldDecoder(); + + static World fromMap(Map map) { + return World( + id: map['id'] as int?, randomNumber: map['random_number'] as int?); + } + + static Map toMap(_World? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'random_number': model.randomNumber}; + } +} + +abstract class WorldFields { + static const List allFields = [ + id, + randomNumber, + ]; + + static const String id = 'id'; + + static const String randomNumber = 'random_number'; +} diff --git a/packages/orm/angel_orm_mysql/test/orm_debug.dart b/packages/orm/angel_orm_mysql/test/orm_debug.dart deleted file mode 100644 index d1ebebe47..000000000 --- a/packages/orm/angel_orm_mysql/test/orm_debug.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:logging/logging.dart'; - -void main() async { - //hierarchicalLoggingEnabled = true; - Logger.root - ..level = Level.INFO - ..onRecord.listen(print); - //Logger.root.onRecord.listen((rec) { - // print(rec); - // if (rec.error != null) print(rec.error); - // if (rec.stackTrace != null) print(rec.stackTrace); - //}); - - //belongsToTests(createTables(['author', 'book']), close: dropTables); - - //hasOneTests(my(['leg', 'foot']), close: closeMy); - //standaloneTests(my(['car']), close: closeMy); -} diff --git a/packages/orm/angel_orm_mysql/test/performance_test.dart b/packages/orm/angel_orm_mysql/test/performance_test.dart new file mode 100644 index 000000000..c36770485 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/performance_test.dart @@ -0,0 +1,94 @@ +// ignore: library_annotations +@Skip('Only used for debugging issues') + +import 'dart:async'; +import 'dart:math'; + +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:test/test.dart'; + +import 'models/fortune.dart'; +import 'models/world.dart'; + +void performanceTests(FutureOr Function() createExecutor, + {FutureOr Function(QueryExecutor)? close}) { + QueryExecutor? executor; + + //int _sampleSize = 1000; + + int concurrency = 200; + + // Generate a random number between 1 and 10000 + int genRandomId() { + var rand = Random(); + return rand.nextInt(10000) + 1; + } + + setUp(() async { + print("Run setup"); + executor = await createExecutor(); + + /* + for (var i = 0; i < _sampleSize; i++) { + var query = WorldQuery(); + query.values.randomNumber = _genRandomId(); + var world = (await query.insert(executor!)).value; + } + + for (var i = 0; i < _sampleSize; i++) { + var query = FortuneQuery(); + query.values.message = "message ${_genRandomId()}"; + var fortune = (await query.insert(executor!)).value; + } + */ + }); + + tearDown(() => close!(executor!)); + + test('select all concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + // Fire and forget + for (var i = 0; i < concurrency; i++) { + FortuneQuery().get(executor!); + } + + print("Loop time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + + var result = await FortuneQuery().get(executor!); + + print("Final Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); + + test('select one concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + var id = genRandomId(); + var query = WorldQuery()..where?.id.equals(id); + var result = await query.get(executor!); + + print("Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); + + test('update concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + var id = genRandomId(); + var query = WorldQuery()..where?.id.equals(id); + var result = await query.get(executor!); + + print("Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/standalone_test.dart b/packages/orm/angel_orm_mysql/test/standalone_test.dart new file mode 100644 index 000000000..8b6072c97 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/standalone_test.dart @@ -0,0 +1,228 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:mysql_client/mysql_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/car.dart'; + +final DateTime y2k = DateTime(2000, 1, 1); + +void main() { + late MySQLConnection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openMySqlConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [CarMigration()]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('to where', () { + var query = CarQuery(); + query.where + ?..familyFriendly.isTrue + ..recalledAt.lessThanOrEqualTo(y2k, includeTime: false); + var whereClause = query.where?.compile(tableName: 'cars'); + //print('Where clause: $whereClause'); + expect(whereClause, + 'cars.family_friendly = TRUE AND cars.recalled_at <= \'2000-01-01\''); + }); + + test('parseRow', () { + // 'id', 'created_at', 'updated_at', 'make', 'description', 'family_friendly', 'recalled_at' + // var row = [0, 'Mazda', 'CX9', true, y2k, y2k, y2k]; + var row = [0, y2k, y2k, 'Mazda', 'CX9', true, y2k, 80000.00]; + //print(row); + var carOpt = CarQuery().deserialize(row); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + //print(car.toJson()); + expect(car.id, '0'); + expect(car.make, 'Mazda'); + expect(car.description, 'CX9'); + expect(car.familyFriendly, true); + expect( + y2k.toIso8601String(), startsWith(car.recalledAt!.toIso8601String())); + expect( + y2k.toIso8601String(), startsWith(car.createdAt!.toIso8601String())); + expect( + y2k.toIso8601String(), startsWith(car.updatedAt!.toIso8601String())); + expect(car.price, 80000.00); + }); + }); + + group('queries', () { + group('selects', () { + test('select all', () async { + var cars = await CarQuery().get(executor); + expect(cars, []); + }); + + group('with data', () { + Car? ferrari; + + setUp(() async { + var query = CarQuery(); + query.values + ..make = 'Ferrarię±' + ..createdAt = y2k + ..updatedAt = y2k + ..description = 'Vroom vroom!' + ..price = 120000.00 + ..familyFriendly = false; + ferrari = (await query.insert(executor)).value; + }); + + test('where clause is applied', () async { + var query = CarQuery()..where!.familyFriendly.isTrue; + var cars = await query.get(executor); + expect(cars, isEmpty); + + var sportsCars = CarQuery()..where!.familyFriendly.isFalse; + cars = await sportsCars.get(executor); + print(cars.map((c) => c.toJson())); + + var car = cars.first; + expect(car.make, ferrari!.make); + expect(car.description, ferrari!.description); + expect(car.familyFriendly, ferrari!.familyFriendly); + expect(car.recalledAt, isNull); + }); + + test('union', () async { + var query1 = CarQuery()..where?.make.like('%Fer%'); + var query2 = CarQuery()..where?.familyFriendly.isTrue; + var query3 = CarQuery()..where?.description.equals('Submarine'); + var union = query1.union(query2).unionAll(query3); + //print(union.compile({})); + var cars = await union.get(executor); + expect(cars, hasLength(1)); + }); + + test('or clause', () async { + var query = CarQuery() + ..where!.make.like('Fer%') + ..orWhere((where) => where + ..familyFriendly.isTrue + ..make.equals('Honda')); + //print(query.compile({})); + var cars = await query.get(executor); + expect(cars, hasLength(1)); + }); + + test('limit obeyed', () async { + var query = CarQuery()..limit(0); + //print(query.compile({})); + var cars = await query.get(executor); + expect(cars, isEmpty); + }); + + test('get one', () async { + var id = int.parse(ferrari!.id!); + var query = CarQuery()..where!.id.equals(id); + var carOpt = await query.getOne(executor); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + expect(car, ferrari); + }); + }); + + test('delete one', () async { + var id = int.parse(ferrari!.id!); + var query = CarQuery()..where!.id.equals(id); + var carOpt = await query.deleteOne(executor); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + expect(car.toJson(), ferrari!.toJson()); + }); + + var cars = await CarQuery().get(executor); + expect(cars, isEmpty); + }); + + test('delete stream', () async { + var query = CarQuery() + ..where!.make.equals('Ferrarię±') + ..orWhere((w) => w.familyFriendly.isTrue); + //print(query.compile({}, preamble: 'DELETE FROM "cars"')); + + var cars = await query.delete(executor); + expect(cars, hasLength(1)); + expect(cars.first.toJson(), ferrari!.toJson()); + }); + + test('update', () async { + var query = CarQuery() + ..where!.id.equals(int.parse(ferrari!.id!)) + ..values.make = 'Hyundai'; + var cars = await query.update(executor); + expect(cars, hasLength(1)); + expect(cars.first.make, 'Hyundai'); + }); + + test('update car', () async { + var cloned = ferrari!.copyWith(make: 'Angel'); + var query = CarQuery()..values.copyFrom(cloned); + var carOpt = await (query.updateOne(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + //print(car.toJson()); + expect(car.toJson(), cloned.toJson()); + }); + }); + }); + }); + + test('insert', () async { + var recalledAt = DateTime(2000, 1, 1, 0, 0, 0, 0, 0); + var query = CarQuery(); + var now = DateTime.now(); + query.values + ..make = 'Honda' + ..description = 'Hello' + ..familyFriendly = true + ..recalledAt = recalledAt + ..createdAt = now + ..updatedAt = now; + var carOpt = await (query.insert(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + expect(car.id, isNotNull); + expect(car.make, 'Honda'); + expect(car.description, 'Hello'); + expect(car.familyFriendly, isTrue); + expect( + dateYmdHms.format(car.recalledAt!), dateYmdHms.format(recalledAt)); + expect(dateYmdHms.format(car.createdAt!), dateYmdHms.format(now)); + }); + }); + + test('insert car', () async { + var recalledAt = DateTime.now(); + var beetle = Car( + make: 'Beetle', + description: 'Herbie', + familyFriendly: true, + recalledAt: recalledAt); + var query = CarQuery()..values.copyFrom(beetle); + var carOpt = await (query.insert(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + //print(car.toJson()); + expect(car.make, beetle.make); + expect(car.description, beetle.description); + expect(car.familyFriendly, beetle.familyFriendly); + expect(dateYmdHms.format(car.recalledAt!), + dateYmdHms.format(beetle.recalledAt!)); + }); + }); + }); +} diff --git a/packages/orm/angel_orm_mysql/test/util.dart b/packages/orm/angel_orm_mysql/test/util.dart new file mode 100644 index 000000000..f1eb1d622 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/util.dart @@ -0,0 +1,17 @@ +import 'dart:io'; +import 'package:io/ansi.dart'; + +void printSeparator(String title) { + var b = StringBuffer('===${title.toUpperCase()}'); + + int columns = 80; + if (stdout.hasTerminal) { + columns = stdout.terminalColumns - 3; + } + for (var i = b.length; i < columns; i++) { + b.write('='); + } + for (var i = 0; i < 3; i++) { + print(magenta.wrap(b.toString())); + } +} diff --git a/packages/orm/angel_orm_postgres/CHANGELOG.md b/packages/orm/angel_orm_postgres/CHANGELOG.md index ff0c445a4..502bbc297 100644 --- a/packages/orm/angel_orm_postgres/CHANGELOG.md +++ b/packages/orm/angel_orm_postgres/CHANGELOG.md @@ -2,10 +2,13 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release * Take @SerializableField properties into account when generating `Query.parseRow` (#98) +* Removed dependency on `angel3_orm_test` +* Migrated test cases from `angel3_orm_test` +* Skipped test cases for nullable primary key ## 8.2.2 diff --git a/packages/orm/angel_orm_postgres/README.md b/packages/orm/angel_orm_postgres/README.md index d78c9d9cb..a32d367ba 100644 --- a/packages/orm/angel_orm_postgres/README.md +++ b/packages/orm/angel_orm_postgres/README.md @@ -5,16 +5,10 @@ [![Discord](https://img.shields.io/discord/1060322353214660698)](https://discord.gg/3X6bxTUdCM) [![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_postgres/LICENSE) -PostgreSQL support for Angel3 ORM. - -## Supported database - -* PostgreSQL version 10 or greater +Angel3 ORM for PostgreSQL database for PostgreSQL 10 or greater. For documentation about the ORM, see [Developer Guide](https://angel3-docs.dukefirehawk.com/guides/orm) -## Migration - -### From version 7.x to 8.1.x +## Migrating to 8.1.0 and above `postgres` has been upgraded from 2.x.x to 3.x.x since version 8.1.0. This is a breaking change as `postgres` 3.x.x has majorly changed its API. Therefore when upgrading to 8.1.0 and beyond, the PostgreSQL connection settings need to be migrated. The rest should remain the same. Please see the example for the new PostgreSQL connection settings. diff --git a/packages/orm/angel_orm_postgres/pubspec.yaml b/packages/orm/angel_orm_postgres/pubspec.yaml index e96b31635..4fdb328bc 100644 --- a/packages/orm/angel_orm_postgres/pubspec.yaml +++ b/packages/orm/angel_orm_postgres/pubspec.yaml @@ -4,7 +4,7 @@ description: PostgreSQL support for Angel3 ORM. Includes functionality for query homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_postgres environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_orm: ^8.2.0 logging: ^1.2.0 @@ -12,22 +12,27 @@ dependencies: postgres: ^3.2.0 dev_dependencies: belatuk_pretty_logging: ^6.1.0 - angel3_orm_generator: ^8.3.0 - angel3_orm_test: ^8.2.0 + angel3_orm_generator: ^8.2.0 +# angel3_orm_test: ^8.2.0 + angel3_migration: ^8.0.0 + angel3_migration_runner: ^8.0.0 test: ^1.24.0 lints: ^5.0.0 -#dependency_overrides: + build_runner: ^2.4.0 +dependency_overrides: # angel3_serialize: # path: ../../serialize/angel_serialize -# angel3_serialize_generator: -# path: ../../serialize/angel_serialize_generator + angel3_serialize_generator: + path: ../../serialize/angel_serialize_generator # angel3_model: # path: ../../model # angel3_orm_test: # path: ../angel_orm_test -# angel3_orm_generator: -# path: ../angel_orm_generator + angel3_orm_generator: + path: ../angel_orm_generator # angel3_orm: # path: ../angel_orm # angel3_migration: # path: ../angel_migration + angel3_migration_runner: + path: ../angel_migration_runner diff --git a/packages/orm/angel_orm_postgres/test/all_test.dart b/packages/orm/angel_orm_postgres/test/all_test.dart deleted file mode 100644 index 4d7b73747..000000000 --- a/packages/orm/angel_orm_postgres/test/all_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:angel3_orm_test/angel3_orm_test.dart'; -import 'package:logging/logging.dart'; -import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart'; -import 'package:test/test.dart'; -import 'common.dart'; - -void main() { - Logger.root - ..level = Level.ALL - ..onRecord.listen(prettyLog); - - //group('performance', - // () => performanceTests(pg(['performance']), close: closePg)); - - group('postgresql', () { - group('belongsTo', - () => belongsToTests(pg(['author', 'book']), close: closePg)); - group('customExpr', - () => customExprTests(pg(['custom_expr']), close: closePg)); - group( - 'edgeCase', - () => edgeCaseTests(pg(['unorthodox', 'weird_join', 'song', 'numba']), - close: closePg)); - group('enumAndNested', - () => enumAndNestedTests(pg(['has_car']), close: closePg)); - group('hasMany', () => hasManyTests(pg(['tree', 'fruit']), close: closePg)); - group('hasMap', () => hasMapTests(pg(['has_map']), close: closePg)); - group('hasOne', () => hasOneTests(pg(['leg', 'foot']), close: closePg)); - group( - 'manyToMany', - () => - manyToManyTests(pg(['user', 'role', 'user_role']), close: closePg)); - group('standalone', () => standaloneTests(pg(['car']), close: closePg)); - group('join', - () => joinTests(pg(['person', 'person_order']), close: closePg)); - }); -} diff --git a/packages/orm/angel_orm_postgres/test/belongs_to_test.dart b/packages/orm/angel_orm_postgres/test/belongs_to_test.dart new file mode 100644 index 000000000..c410d269e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/belongs_to_test.dart @@ -0,0 +1,179 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/book.dart'; + +import 'util.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Author? jkRowling; + Author? jameson; + Book? deathlyHallows; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [AuthorMigration(), BookMigration()]); + + // Insert an author + var query = AuthorQuery()..values.name = 'J.K. Rowling'; + jkRowling = (await query.insert(executor)).value; + + query.values.name = 'J.K. Jameson'; + jameson = (await query.insert(executor)).value; + + // And a book + var bookQuery = BookQuery(); + bookQuery.values + ..authorId = jkRowling?.idAsInt ?? 0 + ..partnerAuthorId = jameson?.idAsInt ?? 0 + ..name = 'Deathly Hallows'; + + deathlyHallows = (await bookQuery.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + group('selects', () { + test('select all', () async { + var query = BookQuery(); + var books = await query.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('select one', () async { + var query = BookQuery(); + query.where!.id.equals(int.parse(deathlyHallows!.id!)); + //print(query.compile({})); + + var bookOpt = await query.getOne(executor); + expect(bookOpt.isPresent, true); + bookOpt.ifPresent((book) { + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + }); + + test('where clause', () async { + var query = BookQuery() + ..where!.name.equals('Goblet of Fire') + ..orWhere((w) => w.authorId.equals(int.parse(jkRowling!.id!))); + //print(query.compile({})); + + var books = await query.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('union', () async { + var query1 = BookQuery()..where!.name.like('Deathly%'); + var query2 = BookQuery()..where!.authorId.equals(-1); + var query3 = BookQuery() + ..where!.name.isIn(['Goblet of Fire', 'Order of the Phoenix']); + query1 + ..union(query2) + ..unionAll(query3); + //print(query1.compile({})); + + var books = await query1.get(executor); + expect(books, hasLength(1)); + + var book = books.first; + //print(book.toJson()); + expect(book.id, deathlyHallows!.id); + expect(book.name, deathlyHallows!.name); + + var author = book.author!; + //print(AuthorSerializer.toMap(author)); + expect(author.id, jkRowling!.id); + expect(author.name, jkRowling!.name); + }); + + test('order by', () async { + var query = AuthorQuery()..orderBy(AuthorFields.name, descending: true); + var authors = await query.get(executor); + expect(authors, [jkRowling, jameson]); + }); + }); + + test('insert sets relationship', () { + expect(deathlyHallows!.author, jkRowling); + //expect(deathlyHallows.author, isNotNull); + //expect(deathlyHallows.author.name, rowling.name); + }); + + test('delete stream', () async { + //printSeparator('Delete stream test'); + var query = BookQuery()..where!.name.equals(deathlyHallows!.name!); + //print(query.compile({}, preamble: 'DELETE', withFields: false)); + var books = await query.delete(executor); + expect(books, hasLength(1)); + + var book = books.first; + expect(book.id, deathlyHallows?.id); + expect(book.author, isNotNull); + expect(book.author!.name, jkRowling!.name); + }); + + test('update book', () async { + var cloned = deathlyHallows!.copyWith(name: "Sorcerer's Stone"); + var query = BookQuery() + ..where?.id.equals(int.parse(cloned.id!)) + ..values.copyFrom(cloned); + var bookOpt = await (query.updateOne(executor)); + expect(bookOpt.isPresent, true); + bookOpt.ifPresent((book) { + //print(book.toJson()); + expect(book.name, cloned.name); + expect(book.author, isNotNull); + expect(book.author!.name, jkRowling!.name); + }); + }); + + group('joined subquery', () { + // To verify that the joined subquery is correct, + // we test both a query that return empty, and one + // that should return correctly. + test('returns empty on false subquery', () async { + printSeparator('False subquery test'); + var query = BookQuery()..author.where!.name.equals('Billie Jean'); + expect(await query.get(executor), isEmpty); + }); + + test('returns values on true subquery', () async { + printSeparator('True subquery test'); + var query = BookQuery()..author.where!.name.like('%Rowling%'); + expect(await query.get(executor), [deathlyHallows]); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/common.dart b/packages/orm/angel_orm_postgres/test/common.dart index 0b04bcd7a..9fe4fa2bb 100644 --- a/packages/orm/angel_orm_postgres/test/common.dart +++ b/packages/orm/angel_orm_postgres/test/common.dart @@ -1,104 +1,54 @@ import 'dart:async'; import 'dart:io'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_migration_runner/postgres.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; import 'package:logging/logging.dart'; import 'package:postgres/postgres.dart'; -FutureOr Function() pg(Iterable schemas) { - // Use single connection - return () => connectToPostgres(schemas); +List tmpTables = []; - // Use PostgreSqlExecutorPool (Not working) - //return () => connectToPostgresPool1(schemas); -} - -Future closePg(QueryExecutor executor) async { - if (executor is PostgreSqlExecutor) { - await executor.close(); - } -} - -Future connectToPostgres(Iterable schemas) async { - var host = Platform.environment['POSTGRES_HOST'] ?? 'localhost'; - var database = Platform.environment['POSTGRES_DB'] ?? 'orm_test'; - var username = Platform.environment['POSTGRES_USERNAME'] ?? 'test'; - var password = Platform.environment['POSTGRES_PASSWORD'] ?? 'test123'; - - var conn = await Connection.open( +// For PostgreSQL +Future openPgConnection() async { + final connection = await Connection.open( Endpoint( - host: host, + host: Platform.environment['POSTGRES_HOSTNAME'] ?? 'localhost', port: 5432, - database: database, - username: username, - password: password), + database: Platform.environment['POSTGRES_DB'] ?? 'postgres', + username: Platform.environment['POSTGRES_USERNAME'] ?? 'postgres', + password: Platform.environment['POSTGRES_PASSWORD'] ?? 'postgres'), settings: ConnectionSettings(sslMode: SslMode.disable)); - // Run sql to create the tables - for (var s in schemas) { - var rawQueryString = await File('test/migrations/$s.sql').readAsString(); - print("Raw SQL Query: $rawQueryString"); - //await conn.execute(queryString); + return connection; +} - // Split the queries and execute them - var stringLen = rawQueryString.length; - var index = 0; - while (index < stringLen) { - index = rawQueryString.indexOf(";"); - if (index < 0) { - break; - } - var query = rawQueryString.substring(0, index); - print("SQL Query: $query;"); - await conn.execute("$query;"); +Future createExecutor(Connection conn) async { + var logger = Logger('orm_postgres'); - index++; - if (index < stringLen) { - var tempString = rawQueryString.substring(index).trim(); - rawQueryString = tempString; - stringLen = rawQueryString.length; - index = 0; - } - } - /* - var queryString = rawQueryString.replaceAll("\n", " "); - print("Raw Query: $queryString"); - var queries = queryString.split(';'); - for (var rawQuery in queries) { - var query = rawQuery.trim(); - if (query.isNotEmpty) { - print("SQL Query: $query;"); - await conn.execute("$query;"); - } - } - */ - } + return PostgreSqlExecutor(conn, logger: logger); +} + +Future createTables( + Connection conn, List models) async { + var runner = PostgresMigrationRunner(conn, migrations: models); + await runner.up(); - return PostgreSqlExecutor(conn, logger: Logger.root); + return runner; } -Future connectToPostgresPool( - Iterable schemas) async { - var dbPool = Pool.withEndpoints([ - Endpoint( - host: 'localhost', - port: 5432, - database: Platform.environment['POSTGRES_DB'] ?? 'orm_test', - username: Platform.environment['POSTGRES_USERNAME'] ?? 'test', - password: Platform.environment['POSTGRES_PASSWORD'] ?? 'test123', - ) - ], - settings: PoolSettings( - maxConnectionAge: Duration(hours: 1), - maxConnectionCount: 5, - sslMode: SslMode.disable)); +Future dropTables(MigrationRunner runner) async { + await runner.reset(); +} + +String extractTableName(String createQuery) { + var start = createQuery.indexOf('EXISTS'); + var end = createQuery.indexOf('('); - // Run sql to create the tables in a transaction - await dbPool.runTx((conn) async { - for (var s in schemas) { - await conn.execute(await File('test/migrations/$s.sql').readAsString()); - } - }); + if (start == -1 || end == -1) { + return ''; + } - return PostgreSqlPoolExecutor(dbPool, logger: Logger.root); + return createQuery.substring(start + 6, end).trim(); } diff --git a/packages/orm/angel_orm_postgres/test/commondb.dart b/packages/orm/angel_orm_postgres/test/commondb.dart new file mode 100644 index 000000000..0b04bcd7a --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/commondb.dart @@ -0,0 +1,104 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; +import 'package:logging/logging.dart'; +import 'package:postgres/postgres.dart'; + +FutureOr Function() pg(Iterable schemas) { + // Use single connection + return () => connectToPostgres(schemas); + + // Use PostgreSqlExecutorPool (Not working) + //return () => connectToPostgresPool1(schemas); +} + +Future closePg(QueryExecutor executor) async { + if (executor is PostgreSqlExecutor) { + await executor.close(); + } +} + +Future connectToPostgres(Iterable schemas) async { + var host = Platform.environment['POSTGRES_HOST'] ?? 'localhost'; + var database = Platform.environment['POSTGRES_DB'] ?? 'orm_test'; + var username = Platform.environment['POSTGRES_USERNAME'] ?? 'test'; + var password = Platform.environment['POSTGRES_PASSWORD'] ?? 'test123'; + + var conn = await Connection.open( + Endpoint( + host: host, + port: 5432, + database: database, + username: username, + password: password), + settings: ConnectionSettings(sslMode: SslMode.disable)); + + // Run sql to create the tables + for (var s in schemas) { + var rawQueryString = await File('test/migrations/$s.sql').readAsString(); + print("Raw SQL Query: $rawQueryString"); + //await conn.execute(queryString); + + // Split the queries and execute them + var stringLen = rawQueryString.length; + var index = 0; + while (index < stringLen) { + index = rawQueryString.indexOf(";"); + if (index < 0) { + break; + } + var query = rawQueryString.substring(0, index); + print("SQL Query: $query;"); + await conn.execute("$query;"); + + index++; + if (index < stringLen) { + var tempString = rawQueryString.substring(index).trim(); + rawQueryString = tempString; + stringLen = rawQueryString.length; + index = 0; + } + } + /* + var queryString = rawQueryString.replaceAll("\n", " "); + print("Raw Query: $queryString"); + var queries = queryString.split(';'); + for (var rawQuery in queries) { + var query = rawQuery.trim(); + if (query.isNotEmpty) { + print("SQL Query: $query;"); + await conn.execute("$query;"); + } + } + */ + } + + return PostgreSqlExecutor(conn, logger: Logger.root); +} + +Future connectToPostgresPool( + Iterable schemas) async { + var dbPool = Pool.withEndpoints([ + Endpoint( + host: 'localhost', + port: 5432, + database: Platform.environment['POSTGRES_DB'] ?? 'orm_test', + username: Platform.environment['POSTGRES_USERNAME'] ?? 'test', + password: Platform.environment['POSTGRES_PASSWORD'] ?? 'test123', + ) + ], + settings: PoolSettings( + maxConnectionAge: Duration(hours: 1), + maxConnectionCount: 5, + sslMode: SslMode.disable)); + + // Run sql to create the tables in a transaction + await dbPool.runTx((conn) async { + for (var s in schemas) { + await conn.execute(await File('test/migrations/$s.sql').readAsString()); + } + }); + + return PostgreSqlPoolExecutor(dbPool, logger: Logger.root); +} diff --git a/packages/orm/angel_orm_postgres/test/custom_expr_test.dart b/packages/orm/angel_orm_postgres/test/custom_expr_test.dart new file mode 100644 index 000000000..569445104 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/custom_expr_test.dart @@ -0,0 +1,54 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/custom_expr.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + late Numbers numbersModel; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = + await createTables(conn, [NumbersMigration(), AlphabetMigration()]); + + var now = DateTime.now(); + var nQuery = NumbersQuery(); + nQuery.values + ..createdAt = now + ..updatedAt = now; + var numbersModelOpt = await nQuery.insert(executor); + numbersModelOpt.ifPresent((v) { + numbersModel = v; + }); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('fetches correct result', () async { + expect(numbersModel.two, 2); + }); + + test('in relation', () async { + var abcQuery = AlphabetQuery(); + abcQuery.values + ..value = 'abc' + ..numbersId = numbersModel.idAsInt + ..createdAt = numbersModel.createdAt + ..updatedAt = numbersModel.updatedAt; + var abcOpt = await (abcQuery.insert(executor)); + expect(abcOpt.isPresent, true); + abcOpt.ifPresent((abc) { + expect(abc.numbers, numbersModel); + expect(abc.numbers?.two, 2); + expect(abc.value, 'abc'); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/edge_case_test.dart b/packages/orm/angel_orm_postgres/test/edge_case_test.dart new file mode 100644 index 000000000..a4e4afb68 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/edge_case_test.dart @@ -0,0 +1,139 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/unorthodox.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [ + UnorthodoxMigration(), + WeirdJoinMigration(), + SongMigration(), + NumbaMigration(), + FooMigration(), + FooPivotMigration() + ]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('can create object with no id', () async { + var query = UnorthodoxQuery()..values.name = 'World'; + var modelOpt = await query.insert(executor); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + expect(model, Unorthodox(name: 'World')); + }); + }, skip: "Primary key cannot be null"); + + group('relations on non-model', () { + Unorthodox? unorthodox; + + setUp(() async { + //if (unorthodox == null) { + var query = UnorthodoxQuery()..values.name = 'Hey'; + + var unorthodoxOpt = await query.insert(executor); + unorthodoxOpt.ifPresent((value) { + unorthodox = value; + }); + //} + }); + + test('belongs to', () async { + var query = WeirdJoinQuery()..values.joinName = unorthodox!.name; + var modelOpt = await query.insert(executor); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + //print(model.toJson()); + expect(model.id, isNotNull); // Postgres should set this. + expect(model.unorthodox, unorthodox); + }); + }); + + group('layered', () { + WeirdJoin? weirdJoin; + Song? girlBlue; + + setUp(() async { + var wjQuery = WeirdJoinQuery()..values.joinName = unorthodox!.name; + + var weirdJoinOpt = await wjQuery.insert(executor); + //weirdJoin = (await wjQuery.insert(executor)).value; + weirdJoinOpt.ifPresent((value1) async { + weirdJoin = value1; + var gbQuery = SongQuery() + ..values.weirdJoinId = value1.id + ..values.title = 'Girl Blue'; + + var girlBlueOpt = await gbQuery.insert(executor); + girlBlueOpt.ifPresent((value2) { + girlBlue = value2; + }); + }); + }); + + test('has one', () async { + var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id); + var wjOpt = await query.getOne(executor); + expect(wjOpt.isPresent, true); + wjOpt.ifPresent((wj) { + //print(wj.toJson()); + expect(wj.song, girlBlue); + }); + }); + + test('has many', () async { + var numbas = []; + + for (var i = 0; i < 15; i++) { + var query = NumbaQuery() + ..values.parent = weirdJoin!.id + ..values.i = i; + var modelObj = await query.insert(executor); + expect(modelObj.isPresent, true); + modelObj.ifPresent((model) { + numbas.add(model); + }); + } + + var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id); + var wjObj = await query.getOne(executor); + expect(wjObj.isPresent, true); + wjObj.ifPresent((wj) { + //print(wj.toJson()); + expect(wj.numbas, numbas); + }); + }); + + test('many to many', () async { + var fooQuery = FooQuery()..values.bar = 'baz'; + var fooBar = + await fooQuery.insert(executor).then((foo) => foo.value.bar); + var pivotQuery = FooPivotQuery() + ..values.weirdJoinId = weirdJoin!.id + ..values.fooBar = fooBar; + await pivotQuery.insert(executor); + fooQuery = FooQuery()..where!.bar.equals('baz'); + + var fooOpt = await fooQuery.getOne(executor); + expect(fooOpt.isPresent, true); + fooOpt.ifPresent((foo) { + //print(foo.toJson()); + //print(weirdJoin!.toJson()); + expect(foo.weirdJoins[0].id, weirdJoin!.id); + }); + }); + }); + }, skip: 'Primary key cannot be null'); +} diff --git a/packages/orm/angel_orm_postgres/test/enum_and_nested_test.dart b/packages/orm/angel_orm_postgres/test/enum_and_nested_test.dart new file mode 100644 index 000000000..5908dc46c --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/enum_and_nested_test.dart @@ -0,0 +1,55 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/has_car.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [HasCarMigration()]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('insert', () async { + var query = HasCarQuery() + ..values.type = CarType.sedan + ..values.color = Color.red; + var resultOpt = await (query.insert(executor)); + expect(resultOpt.isPresent, true); + resultOpt.ifPresent((result) { + expect(result.type, CarType.sedan); + expect(result.color, Color.red); + }); + }); + + group('query', () { + HasCar? initialValue; + + setUp(() async { + var query = HasCarQuery(); + query.values.type = CarType.sedan; + initialValue = (await query.insert(executor)).value; + }); + + test('query by enum', () async { + // Check for mismatched type + var query = HasCarQuery()..where!.type.equals(CarType.atv); + var result = await query.get(executor); + expect(result, isEmpty); + + query = HasCarQuery()..where!.type.equals(initialValue!.type); + var oneResult = await query.getOne(executor); + expect(oneResult.value, initialValue); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/enum_test.dart b/packages/orm/angel_orm_postgres/test/enum_test.dart new file mode 100644 index 000000000..185e75293 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/enum_test.dart @@ -0,0 +1,12 @@ +import 'package:test/test.dart'; + +import 'models/has_car.dart'; + +void main() async { + /// See https://github.com/dart-backend/angel/pull/98 + test('enum field with custom deserializer should be parsed consistently', () { + final query = HasCarQuery(); + final hasCar = query.parseRow([null, null, null, 'R', null]).value; + expect(hasCar.color, equals(Color.red)); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/has_many_test.dart b/packages/orm/angel_orm_postgres/test/has_many_test.dart new file mode 100644 index 000000000..3540bec55 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/has_many_test.dart @@ -0,0 +1,101 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/tree.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Tree? appleTree; + late int treeId; + + setUp(() async { + var query = TreeQuery()..values.rings = 10; + + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [TreeMigration(), FruitMigration()]); + appleTree = (await query.insert(executor)).value; + treeId = int.parse(appleTree!.id!); + }); + + tearDown(() async => await dropTables(runner)); + + test('list is empty if there is nothing', () { + expect(appleTree!.rings, 10); + expect(appleTree!.fruits, isEmpty); + }); + + group('mutations', () { + Fruit? apple, banana; + + void verify(Tree tree) { + //print(tree.fruits!.map(FruitSerializer.toMap).toList()); + expect(tree.fruits, hasLength(2)); + expect(tree.fruits[0].commonName, apple!.commonName); + expect(tree.fruits[1].commonName, banana!.commonName); + } + + setUp(() async { + var appleQuery = FruitQuery() + ..values.treeId = treeId + ..values.commonName = 'Apple'; + + var bananaQuery = FruitQuery() + ..values.treeId = treeId + ..values.commonName = 'Banana'; + var appleOpt = await appleQuery.insert(executor); + var bananaOpt = await bananaQuery.insert(executor); + appleOpt.ifPresent((a) { + apple = a; + }); + bananaOpt.ifPresent((a) { + banana = a; + }); + }); + + test('can fetch any children', () async { + var query = TreeQuery()..where!.id.equals(treeId); + var treeOpt = await (query.getOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + }); + }); + + test('sets on update', () async { + var tq = TreeQuery() + ..where!.id.equals(treeId) + ..values.rings = 24; + var treeOpt = await (tq.updateOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + expect(tree.rings, 24); + }); + }); + + test('sets on delete', () async { + var tq = TreeQuery()..where!.id.equals(treeId); + var treeOpt = await (tq.deleteOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + verify(tree); + }); + }); + + test('returns empty on false subquery', () async { + var tq = TreeQuery() + ..where!.id.equals(treeId) + ..fruits.where!.commonName.equals('Kiwi'); + var treeOpt = await (tq.getOne(executor)); + expect(treeOpt.isPresent, true); + treeOpt.ifPresent((tree) { + expect(tree.fruits, isEmpty); + }); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/has_map_test.dart b/packages/orm/angel_orm_postgres/test/has_map_test.dart new file mode 100644 index 000000000..488a30705 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/has_map_test.dart @@ -0,0 +1,119 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/has_map.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [HasMapMigration()]); + }); + + tearDown(() async => await dropTables(runner)); + + test('insert', () async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + var modelOpt = await (query.insert(executor)); + expect(modelOpt.isPresent, true); + modelOpt.ifPresent((model) { + //print(model.toString()); + + var data = HasMap(value: {'foo': 'bar'}, list: ['1', 2, 3.0]); + //print(data.toString()); + + expect(model, data); + }); + }); + + test('update', () async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + var modelOpt = await (query.insert(executor)); + expect(modelOpt.isPresent, true); + if (modelOpt.isPresent) { + var model = modelOpt.value; + //print(model.toJson()); + query = HasMapQuery()..values.copyFrom(model); + var result = await query.updateOne(executor); + expect(result.isPresent, true); + result.ifPresent((m) { + expect(m, model); + }); + } + }); + + group('query', () { + HasMap? initialValue; + + setUp(() async { + var query = HasMapQuery(); + query.values + ..value = {'foo': 'bar'} + ..list = ['1', 2, 3.0]; + initialValue = (await query.insert(executor)).value; + }); + + /* + test('get all', () async { + var query = HasMapQuery(); + expect(await query.get(executor), [initialValue]); + }); + + test('map equals', () async { + var query = HasMapQuery(); + query.where!.value.equals({'foo': 'bar'}); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery(); + query.where?.value.equals({'foo': 'baz'}); + expect(await query.get(executor), isEmpty); + }); + */ + + test('list equals', () async { + var query = HasMapQuery(); + + query.where?.list.equals(['1', 2, 3.0]); + + //print(query.substitutionValues); + + var result = await query.get(executor); + expect(result, [initialValue]); + + query = HasMapQuery(); + query.where?.list.equals(['10', 20, 30.0]); + var result2 = await query.get(executor); + expect(result2, isEmpty); + }); + + /* + test('property equals', () async { + var query = HasMapQuery()..where?.value['foo'].asString?.equals('bar'); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery()..where?.value['foo'].asString?.equals('baz'); + expect(await query.get(executor), []); + }); + + test('index equals', () async { + var query = HasMapQuery()..where?.list[0].asString?.equals('1'); + expect(await query.get(executor), [initialValue]); + + query = HasMapQuery()..where?.list[1].asInt?.equals(3); + expect(await query.get(executor), []); + }); + */ + }); +} diff --git a/packages/orm/angel_orm_postgres/test/has_one_test.dart b/packages/orm/angel_orm_postgres/test/has_one_test.dart new file mode 100644 index 000000000..671acf5c3 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/has_one_test.dart @@ -0,0 +1,129 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/leg.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + Leg? originalLeg; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [LegMigration(), FootMigration()]); + var query = LegQuery()..values.name = 'Left'; + originalLeg = (await query.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + test('sets to null if no child', () async { + //print(LegQuery().compile({})); + var query = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var legOpt = await (query.getOne(executor)); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, originalLeg?.name); + expect(leg.id, originalLeg?.id); + expect(leg.foot, isNull); + }); + }); + + test('can fetch one foot', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.getOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.name, originalLeg!.name); + expect(leg.id, originalLeg!.id); + footOpt.ifPresent((foot) { + expect(leg.foot, isNotNull); + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('only fetches one foot even if there are multiple', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 24; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.getOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.name, originalLeg!.name); + expect(leg.id, originalLeg!.id); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets foot on update', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery() + ..where!.id.equals(int.parse(originalLeg!.id!)) + ..values.copyFrom(originalLeg!.copyWith(name: 'Right')); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.updateOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, 'Right'); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets foot on delete', () async { + var footQuery = FootQuery() + ..values.legId = int.parse(originalLeg!.id!) + ..values.nToes = 5.64; + var legQuery = LegQuery()..where!.id.equals(int.parse(originalLeg!.id!)); + var footOpt = await (footQuery.insert(executor)); + var legOpt = await (legQuery.deleteOne(executor)); + expect(footOpt.isPresent, true); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + //print(leg.toJson()); + expect(leg.name, originalLeg?.name); + expect(leg.foot, isNotNull); + footOpt.ifPresent((foot) { + expect(leg.foot!.id, foot.id); + expect(leg.foot!.nToes, foot.nToes); + }); + }); + }); + + test('sets null on false subquery', () async { + var legQuery = LegQuery() + ..where!.id.equals(originalLeg!.idAsInt) + ..foot.where!.legId.equals(originalLeg!.idAsInt + 1024); + var legOpt = await (legQuery.getOne(executor)); + expect(legOpt.isPresent, true); + legOpt.ifPresent((leg) { + expect(leg.foot, isNull); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/join_test.dart b/packages/orm/angel_orm_postgres/test/join_test.dart new file mode 100644 index 000000000..27b10e077 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/join_test.dart @@ -0,0 +1,95 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; + +import 'common.dart'; +import 'models/person.dart'; +import 'models/person_order.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + Person? originalPerson; + PersonOrder? originalOrder1; + PersonOrder? originalOrder2; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = + await createTables(conn, [PersonMigration(), PersonOrderMigration()]); + var query = PersonQuery() + ..values.name = 'DebuggerX' + ..values.age = 29; + originalPerson = (await query.insert(executor)).value; + + var orderQuery = PersonOrderQuery() + ..values.personId = originalPerson!.idAsInt + ..values.name = 'Order1' + ..values.price = 128 + ..values.deleted = false; + + originalOrder1 = (await orderQuery.insert(executor)).value; + + orderQuery = PersonOrderQuery() + ..values.personId = originalPerson!.idAsInt + ..values.name = 'Order2' + ..values.price = 256 + ..values.deleted = true; + + originalOrder2 = (await orderQuery.insert(executor)).value; + }); + + tearDown(() async => await dropTables(runner)); + + test('select person with last order info', () async { + var orderQuery = PersonOrderQuery(); + var query = PersonWithLastOrderQuery(); + query.join( + orderQuery.tableName, PersonFields.id, PersonOrderFields.personId, + alias: 'po'); + query.where?.name.equals(originalPerson!.name!); + query.orderBy('po.id', descending: true); + var personWithOrderInfo = await query.getOne(executor); + expect(personWithOrderInfo.value.lastOrderName, originalOrder2?.name); + }); + + test('select person with last valid order info', () async { + var orderQuery = PersonOrderQuery(); + var query = PersonWithLastOrderQuery(); + query.join( + orderQuery.tableName, PersonFields.id, PersonOrderFields.personId, + alias: 'po'); + query.where?.name.equals(originalPerson!.name!); + query.orderBy('po.id', descending: true); + query.where?.raw('po.deleted = false'); + var personWithOrderInfo = await query.getOne(executor); + expect(personWithOrderInfo.value.lastOrderName, originalOrder1?.name); + }); + + test('select orders with person info', () async { + var personQuery = PersonQuery(); + var query = OrderWithPersonInfoQuery(); + query.join( + personQuery.tableName, PersonOrderFields.personId, PersonFields.id, + alias: 'P'); + query.where?.raw("P.name = '${originalPerson?.name}'"); + var orders = await query.get(executor); + expect( + orders.every((element) => + element.personName == originalPerson?.name && + element.personAge == originalPerson?.age), + true); + }); + + test('select orders with multi order by fields', () async { + var query = PersonOrderQuery(); + query.orderBy(PersonOrderFields.id, descending: true); + query.orderBy(PersonOrderFields.personId, descending: true); + var orders = await query.get(executor); + expect(orders.first.idAsInt > orders.last.idAsInt, true); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/many_to_many_test.dart b/packages/orm/angel_orm_postgres/test/many_to_many_test.dart new file mode 100644 index 000000000..b5783ca38 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/many_to_many_test.dart @@ -0,0 +1,143 @@ +import 'dart:async'; +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/user.dart'; +import 'util.dart'; + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + Role? canPub, canSub; + User? thosakwe; + + /* + Future dumpQuery(String query) async { + if (Platform.environment.containsKey('STFU')) return; + print('\n'); + print('=================================================='); + print(' DUMPING QUERY'); + print(query); + //var rows = await executor.query(null, query, {}); + var rows = await executor.query('', query, {}); + print('\n${rows.length} row(s):'); + for (var r in rows) { + print(' * $r'); + } + print('==================================================\n\n'); + } + */ + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables( + conn, [UserMigration(), RoleMigration(), RoleUserMigration()]); + + var canPubQuery = RoleQuery()..values.name = 'can_pub'; + var canSubQuery = RoleQuery()..values.name = 'can_sub'; + canPub = (await canPubQuery.insert(executor)).value; + print('=== CANPUB: ${canPub?.toJson()}'); + // await dumpQuery(canPubQuery.compile(Set())); + canSub = (await canSubQuery.insert(executor)).value; + print('=== CANSUB: ${canSub?.toJson()}'); + + var thosakweQuery = UserQuery(); + thosakweQuery.values + ..username = 'thosakwe' + ..password = 'Hahahahayoureallythoughtiwasstupidenoughtotypethishere' + ..email = 'thosakwe AT gmail.com'; + var result = await thosakweQuery.insert(executor); + thosakwe = result.value; + print('=== THOSAKWE: ${thosakwe?.toJson()}'); + + // Allow thosakwe to publish... + printSeparator('Allow thosakwe to publish'); + var thosakwePubQuery = RoleUserQuery(); + thosakwePubQuery.values + ..userId = int.parse(thosakwe!.id!) + ..roleId = int.parse(canPub!.id!); + await thosakwePubQuery.insert(executor); + + // Allow thosakwe to subscribe... + printSeparator('Allow thosakwe to subscribe'); + var thosakweSubQuery = RoleUserQuery(); + thosakweSubQuery.values + ..userId = int.parse(thosakwe!.id!) + ..roleId = int.parse(canSub!.id!); + await thosakweSubQuery.insert(executor); + + // Print all users... + // await dumpQuery('select * from users;'); + // await dumpQuery('select * from roles;'); + // await dumpQuery('select * from role_users;'); + // var query = RoleQuery()..where.id.equals(canPub.idAsInt); + // await dumpQuery(query.compile(Set())); + + print('\n'); + print('=================================================='); + print(' GOOD STUFF BEGINS HERE '); + print('==================================================\n\n'); + }); + + tearDown(() async => await dropTables(runner)); + + Future fetchThosakwe() async { + var query = UserQuery()..where!.id.equals(int.parse(thosakwe!.id!)); + var userOpt = await query.getOne(executor); + expect(userOpt.isPresent, true); + if (userOpt.isPresent) { + return userOpt.value; + } else { + return null; + } + } + + test('fetch roles for user', () async { + printSeparator('Fetch roles for user test'); + var user = await fetchThosakwe(); + + expect(user?.roles, hasLength(2)); + expect(user?.roles, contains(canPub)); + expect(user?.roles, contains(canSub)); + }); + + test('fetch users for role', () async { + for (var role in [canPub, canSub]) { + var query = RoleQuery()..where!.id.equals(role!.idAsInt); + var rOpt = await query.getOne(executor); + expect(rOpt.isPresent, true); + rOpt.ifPresent((r) async { + expect(r.users.toList(), [thosakwe]); + }); + } + }); + + test('only fetches linked', () async { + // Create a new user. The roles list should be empty, + // be there are no related rules. + var userQuery = UserQuery(); + userQuery.values + ..username = 'Prince' + ..password = 'Rogers' + ..email = 'Nelson'; + var userOpt = await userQuery.insert(executor); + expect(userOpt.isPresent, true); + if (userOpt.isPresent) { + var user = userOpt.value; + expect(user.roles, isEmpty); + + // Fetch again, just to be doubly sure. + var query = UserQuery()..where!.id.equals(user.idAsInt); + var fetchedOpt = await query.getOne(executor); + expect(fetchedOpt.isPresent, true); + fetchedOpt.ifPresent((fetched) { + expect(fetched.roles, isEmpty); + }); + } + }); +} diff --git a/packages/orm/angel_orm_postgres/test/models/asset.dart b/packages/orm/angel_orm_postgres/test/models/asset.dart new file mode 100644 index 000000000..2a3bc4961 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/asset.dart @@ -0,0 +1,26 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'asset.g.dart'; + +@serializable +@orm +abstract class _Item extends Model { + String get description; +} + +@serializable +@orm +abstract class _Asset extends Model { + String get description; + + String get name; + + @Column(type: ColumnType.numeric, precision: 17, scale: 3) + double get price; + + @hasMany + List<_Item> get items; +} diff --git a/packages/orm/angel_orm_postgres/test/models/asset.g.dart b/packages/orm/angel_orm_postgres/test/models/asset.g.dart new file mode 100644 index 000000000..9376c7dbb --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/asset.g.dart @@ -0,0 +1,787 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class ItemMigration extends Migration { + @override + void up(Schema schema) { + schema.create('items', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('description', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('items'); + } +} + +class AssetMigration extends Migration { + @override + void up(Schema schema) { + schema.create('assets', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('description', length: 255); + table.varChar('name', length: 255); + table.double('price'); + }); + } + + @override + void down(Schema schema) { + schema.drop('assets', cascade: true); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class ItemQuery extends Query { + ItemQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = ItemQueryWhere(this); + } + + @override + final ItemQueryValues values = ItemQueryValues(); + + List _selectedFields = []; + + ItemQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'items'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'description', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + ItemQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + ItemQueryWhere? get where { + return _where; + } + + @override + ItemQueryWhere newWhereClause() { + return ItemQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Item( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + description: fields.contains('description') ? (row[3] as String) : '', + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class ItemQueryWhere extends QueryWhere { + ItemQueryWhere(ItemQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder description; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + description, + ]; + } +} + +class ItemQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + void copyFrom(Item model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + description = model.description; + } +} + +class AssetQuery extends Query { + AssetQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AssetQueryWhere(this); + leftJoin( + _items = ItemQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'asset_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'description', + ], + trampoline: trampoline, + ); + } + + @override + final AssetQueryValues values = AssetQueryValues(); + + List _selectedFields = []; + + AssetQueryWhere? _where; + + late ItemQuery _items; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'assets'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'description', + 'name', + 'price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AssetQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AssetQueryWhere? get where { + return _where; + } + + @override + AssetQueryWhere newWhereClause() { + return AssetQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Asset( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + description: fields.contains('description') ? (row[3] as String) : '', + name: fields.contains('name') ? (row[4] as String) : '', + price: fields.contains('price') ? mapToDouble(row[5]) : 0.0, + ); + if (row.length > 6) { + var modelOpt = ItemQuery().parseRow(row.skip(6).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(items: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + ItemQuery get items { + return _items; + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + items: List<_Item>.from(l.items)..addAll(model.items)); + } + }); + }); + } +} + +class AssetQueryWhere extends QueryWhere { + AssetQueryWhere(AssetQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder description; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + description, + name, + price, + ]; + } +} + +class AssetQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + String get name { + return (values['name'] as String); + } + + set name(String value) => values['name'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + void copyFrom(Asset model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + description = model.description; + name = model.name; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Item extends _Item { + Item({ + this.id, + this.createdAt, + this.updatedAt, + required this.description, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String description; + + Item copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + }) { + return Item( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + description: description ?? this.description); + } + + @override + bool operator ==(other) { + return other is _Item && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.description == description; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + description, + ]); + } + + @override + String toString() { + return 'Item(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, description=$description)'; + } + + Map toJson() { + return ItemSerializer.toMap(this); + } +} + +@generatedSerializable +class Asset extends _Asset { + Asset({ + this.id, + this.createdAt, + this.updatedAt, + required this.description, + required this.name, + required this.price, + List<_Item> items = const [], + }) : items = List.unmodifiable(items); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String description; + + @override + String name; + + @override + double price; + + @override + List<_Item> items; + + Asset copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + String? name, + double? price, + List<_Item>? items, + }) { + return Asset( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + description: description ?? this.description, + name: name ?? this.name, + price: price ?? this.price, + items: items ?? this.items); + } + + @override + bool operator ==(other) { + return other is _Asset && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.description == description && + other.name == name && + other.price == price && + ListEquality<_Item>(DefaultEquality<_Item>()) + .equals(other.items, items); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + description, + name, + price, + items, + ]); + } + + @override + String toString() { + return 'Asset(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, description=$description, name=$name, price=$price, items=$items)'; + } + + Map toJson() { + return AssetSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const ItemSerializer itemSerializer = ItemSerializer(); + +class ItemEncoder extends Converter { + const ItemEncoder(); + + @override + Map convert(Item model) => ItemSerializer.toMap(model); +} + +class ItemDecoder extends Converter { + const ItemDecoder(); + + @override + Item convert(Map map) => ItemSerializer.fromMap(map); +} + +class ItemSerializer extends Codec { + const ItemSerializer(); + + @override + ItemEncoder get encoder => const ItemEncoder(); + + @override + ItemDecoder get decoder => const ItemDecoder(); + + static Item fromMap(Map map) { + return Item( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + description: map['description'] as String); + } + + static Map toMap(_Item? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'description': model.description + }; + } +} + +abstract class ItemFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + description, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String description = 'description'; +} + +const AssetSerializer assetSerializer = AssetSerializer(); + +class AssetEncoder extends Converter { + const AssetEncoder(); + + @override + Map convert(Asset model) => AssetSerializer.toMap(model); +} + +class AssetDecoder extends Converter { + const AssetDecoder(); + + @override + Asset convert(Map map) => AssetSerializer.fromMap(map); +} + +class AssetSerializer extends Codec { + const AssetSerializer(); + + @override + AssetEncoder get encoder => const AssetEncoder(); + + @override + AssetDecoder get decoder => const AssetDecoder(); + + static Asset fromMap(Map map) { + return Asset( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + description: map['description'] as String, + name: map['name'] as String, + price: map['price'] as double, + items: map['items'] is Iterable + ? List.unmodifiable(((map['items'] as Iterable).whereType()) + .map(ItemSerializer.fromMap)) + : []); + } + + static Map toMap(_Asset? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'description': model.description, + 'name': model.name, + 'price': model.price, + 'items': model.items.map((m) => ItemSerializer.toMap(m)).toList() + }; + } +} + +abstract class AssetFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + description, + name, + price, + items, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String description = 'description'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String items = 'items'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/bike.dart b/packages/orm/angel_orm_postgres/test/models/bike.dart new file mode 100644 index 000000000..08db8929b --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/bike.dart @@ -0,0 +1,22 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'bike.g.dart'; + +@serializable +@orm +abstract class _Bike extends Model { + String get make; + + String get description; + + bool get familyFriendly; + + DateTime get recalledAt; + + double get price; + + int get width; +} diff --git a/packages/orm/angel_orm_postgres/test/models/bike.g.dart b/packages/orm/angel_orm_postgres/test/models/bike.g.dart new file mode 100644 index 000000000..8ba17e744 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/bike.g.dart @@ -0,0 +1,481 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bike.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BikeMigration extends Migration { + @override + void up(Schema schema) { + schema.create('bikes', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('make', length: 255); + table.varChar('description', length: 255); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + table.integer('width'); + }); + } + + @override + void down(Schema schema) { + schema.drop('bikes'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BikeQuery extends Query { + BikeQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BikeQueryWhere(this); + } + + @override + final BikeQueryValues values = BikeQueryValues(); + + List _selectedFields = []; + + BikeQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'bikes'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + 'width', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BikeQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BikeQueryWhere? get where { + return _where; + } + + @override + BikeQueryWhere newWhereClause() { + return BikeQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Bike( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String) : '', + description: fields.contains('description') ? (row[4] as String) : '', + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : false, + recalledAt: fields.contains('recalled_at') + ? mapToDateTime(row[6]) + : DateTime.parse("1970-01-01 00:00:00"), + price: fields.contains('price') ? mapToDouble(row[7]) : 0.0, + width: fields.contains('width') ? (row[8] as int) : 0, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class BikeQueryWhere extends QueryWhere { + BikeQueryWhere(BikeQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + width = NumericSqlExpressionBuilder( + query, + 'width', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + final NumericSqlExpressionBuilder width; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + } +} + +class BikeQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get make { + return (values['make'] as String); + } + + set make(String value) => values['make'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + bool get familyFriendly { + return (values['family_friendly'] as bool); + } + + set familyFriendly(bool value) => values['family_friendly'] = value; + + DateTime get recalledAt { + return (values['recalled_at'] as DateTime); + } + + set recalledAt(DateTime value) => values['recalled_at'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + int get width { + return (values['width'] as int); + } + + set width(int value) => values['width'] = value; + + void copyFrom(Bike model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + width = model.width; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Bike extends _Bike { + Bike({ + this.id, + this.createdAt, + this.updatedAt, + required this.make, + required this.description, + required this.familyFriendly, + required this.recalledAt, + required this.price, + required this.width, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String make; + + @override + String description; + + @override + bool familyFriendly; + + @override + DateTime recalledAt; + + @override + double price; + + @override + int width; + + Bike copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + int? width, + }) { + return Bike( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price, + width: width ?? this.width); + } + + @override + bool operator ==(other) { + return other is _Bike && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price && + other.width == width; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]); + } + + @override + String toString() { + return 'Bike(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price, width=$width)'; + } + + Map toJson() { + return BikeSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BikeSerializer bikeSerializer = BikeSerializer(); + +class BikeEncoder extends Converter { + const BikeEncoder(); + + @override + Map convert(Bike model) => BikeSerializer.toMap(model); +} + +class BikeDecoder extends Converter { + const BikeDecoder(); + + @override + Bike convert(Map map) => BikeSerializer.fromMap(map); +} + +class BikeSerializer extends Codec { + const BikeSerializer(); + + @override + BikeEncoder get encoder => const BikeEncoder(); + + @override + BikeDecoder get decoder => const BikeDecoder(); + + static Bike fromMap(Map map) { + return Bike( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String, + description: map['description'] as String, + familyFriendly: map['family_friendly'] as bool, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : DateTime.parse("1970-01-01 00:00:00"), + price: map['price'] as double, + width: map['width'] as int); + } + + static Map toMap(_Bike? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt.toIso8601String(), + 'price': model.price, + 'width': model.width + }; + } +} + +abstract class BikeFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; + + static const String width = 'width'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/boat.d.ts b/packages/orm/angel_orm_postgres/test/models/boat.d.ts new file mode 100644 index 000000000..e7706ea5a --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/boat.d.ts @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +declare module 'angel3_orm_postgres' { + interface Boat { + id?: string; + created_at?: any; + updated_at?: any; + make?: string; + description?: string; + family_friendly?: boolean; + recalled_at?: any; + price?: number; + width?: number; + } +} \ No newline at end of file diff --git a/packages/orm/angel_orm_postgres/test/models/boat.dart b/packages/orm/angel_orm_postgres/test/models/boat.dart new file mode 100644 index 000000000..b1ccf47c8 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/boat.dart @@ -0,0 +1,28 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'boat.g.dart'; + +@Serializable(serializers: Serializers.all) +@orm +abstract class _Boat extends Model { + @Column(defaultValue: '') + String get make; + + @Column(defaultValue: 'none') + String get description; + + @Column(defaultValue: false) + bool get familyFriendly; + + //@SerializableField(defaultValue: '1970-01-01 00:00:01') + DateTime get recalledAt; + + @Column(defaultValue: 0.0) + double get price; + + @Column(defaultValue: 0) + int get width; +} diff --git a/packages/orm/angel_orm_postgres/test/models/boat.g.dart b/packages/orm/angel_orm_postgres/test/models/boat.g.dart new file mode 100644 index 000000000..f207a2e9e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/boat.g.dart @@ -0,0 +1,481 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'boat.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BoatMigration extends Migration { + @override + void up(Schema schema) { + schema.create('boats', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('make', length: 255).defaultsTo(''); + table.varChar('description', length: 255).defaultsTo('none'); + table.boolean('family_friendly').defaultsTo(false); + table.timeStamp('recalled_at'); + table.double('price').defaultsTo(0.0); + table.integer('width').defaultsTo(0); + }); + } + + @override + void down(Schema schema) { + schema.drop('boats'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BoatQuery extends Query { + BoatQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BoatQueryWhere(this); + } + + @override + final BoatQueryValues values = BoatQueryValues(); + + List _selectedFields = []; + + BoatQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'boats'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + 'width', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BoatQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BoatQueryWhere? get where { + return _where; + } + + @override + BoatQueryWhere newWhereClause() { + return BoatQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Boat( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String) : '', + description: fields.contains('description') ? (row[4] as String) : '', + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : false, + recalledAt: fields.contains('recalled_at') + ? mapToDateTime(row[6]) + : DateTime.parse("1970-01-01 00:00:00"), + price: fields.contains('price') ? mapToDouble(row[7]) : 0.0, + width: fields.contains('width') ? (row[8] as int) : 0, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class BoatQueryWhere extends QueryWhere { + BoatQueryWhere(BoatQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + width = NumericSqlExpressionBuilder( + query, + 'width', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + final NumericSqlExpressionBuilder width; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + } +} + +class BoatQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String get make { + return (values['make'] as String); + } + + set make(String value) => values['make'] = value; + + String get description { + return (values['description'] as String); + } + + set description(String value) => values['description'] = value; + + bool get familyFriendly { + return (values['family_friendly'] as bool); + } + + set familyFriendly(bool value) => values['family_friendly'] = value; + + DateTime get recalledAt { + return (values['recalled_at'] as DateTime); + } + + set recalledAt(DateTime value) => values['recalled_at'] = value; + + double get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double value) => values['price'] = value; + + int get width { + return (values['width'] as int); + } + + set width(int value) => values['width'] = value; + + void copyFrom(Boat model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + width = model.width; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Boat extends _Boat { + Boat({ + this.id, + this.createdAt, + this.updatedAt, + required this.make, + required this.description, + required this.familyFriendly, + required this.recalledAt, + required this.price, + required this.width, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String make; + + @override + String description; + + @override + bool familyFriendly; + + @override + DateTime recalledAt; + + @override + double price; + + @override + int width; + + Boat copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + int? width, + }) { + return Boat( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price, + width: width ?? this.width); + } + + @override + bool operator ==(other) { + return other is _Boat && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price && + other.width == width; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]); + } + + @override + String toString() { + return 'Boat(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price, width=$width)'; + } + + Map toJson() { + return BoatSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BoatSerializer boatSerializer = BoatSerializer(); + +class BoatEncoder extends Converter { + const BoatEncoder(); + + @override + Map convert(Boat model) => BoatSerializer.toMap(model); +} + +class BoatDecoder extends Converter { + const BoatDecoder(); + + @override + Boat convert(Map map) => BoatSerializer.fromMap(map); +} + +class BoatSerializer extends Codec { + const BoatSerializer(); + + @override + BoatEncoder get encoder => const BoatEncoder(); + + @override + BoatDecoder get decoder => const BoatDecoder(); + + static Boat fromMap(Map map) { + return Boat( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String, + description: map['description'] as String, + familyFriendly: map['family_friendly'] as bool, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : DateTime.parse("1970-01-01 00:00:00"), + price: map['price'] as double, + width: map['width'] as int); + } + + static Map toMap(_Boat? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt.toIso8601String(), + 'price': model.price, + 'width': model.width + }; + } +} + +abstract class BoatFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + width, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; + + static const String width = 'width'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/book.dart b/packages/orm/angel_orm_postgres/test/models/book.dart new file mode 100644 index 000000000..d4bf2bab4 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/book.dart @@ -0,0 +1,31 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'book.g.dart'; + +@serializable +@orm +abstract class _Book extends Model { + @BelongsTo(joinType: JoinType.inner) + _Author? get author; + + @BelongsTo(localKey: 'partner_author_id', joinType: JoinType.inner) + _Author? partnerAuthor; + + String? get name; +} + +@serializable +@orm +abstract class _Author extends Model { + @Column(length: 255, indexType: IndexType.unique) + @SerializableField(defaultValue: 'Tobe Osakwe') + String? get name; + + @Column(name: "pub") + String? get publisher; +} diff --git a/packages/orm/angel_orm_postgres/test/models/book.g.dart b/packages/orm/angel_orm_postgres/test/models/book.g.dart new file mode 100644 index 000000000..3b487a383 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/book.g.dart @@ -0,0 +1,785 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'book.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class BookMigration extends Migration { + @override + void up(Schema schema) { + schema.create('books', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('name', length: 255); + table + .declare('partner_author_id', ColumnType('int')) + .references('authors', 'id'); + table.declare('author_id', ColumnType('int')).references('authors', 'id'); + }); + } + + @override + void down(Schema schema) { + schema.drop('books'); + } +} + +class AuthorMigration extends Migration { + @override + void up(Schema schema) { + schema.create('authors', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('name', length: 255) + ..defaultsTo('Tobe Osakwe') + ..unique(); + table.varChar('publisher', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('authors'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class BookQuery extends Query { + BookQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = BookQueryWhere(this); + join( + _partnerAuthor = AuthorQuery( + trampoline: trampoline, + parent: this, + ), + 'partner_author_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'publisher', + ], + trampoline: trampoline, + ); + join( + _author = AuthorQuery( + trampoline: trampoline, + parent: this, + ), + 'author_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'publisher', + ], + trampoline: trampoline, + ); + } + + @override + final BookQueryValues values = BookQueryValues(); + + List _selectedFields = []; + + BookQueryWhere? _where; + + late AuthorQuery _partnerAuthor; + + late AuthorQuery _author; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'books'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'partner_author_id', + 'author_id', + 'name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + BookQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + BookQueryWhere? get where { + return _where; + } + + @override + BookQueryWhere newWhereClause() { + return BookQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Book( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[5] as String?) : null, + ); + if (row.length > 6) { + var modelOpt = AuthorQuery().parseRow(row.skip(6).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(partnerAuthor: m); + }); + } + if (row.length > 11) { + var modelOpt = AuthorQuery().parseRow(row.skip(11).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(author: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + AuthorQuery get partnerAuthor { + return _partnerAuthor; + } + + AuthorQuery get author { + return _author; + } +} + +class BookQueryWhere extends QueryWhere { + BookQueryWhere(BookQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + partnerAuthorId = NumericSqlExpressionBuilder( + query, + 'partner_author_id', + ), + authorId = NumericSqlExpressionBuilder( + query, + 'author_id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder partnerAuthorId; + + final NumericSqlExpressionBuilder authorId; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + partnerAuthorId, + authorId, + name, + ]; + } +} + +class BookQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int get partnerAuthorId { + return (values['partner_author_id'] as int); + } + + set partnerAuthorId(int value) => values['partner_author_id'] = value; + + int get authorId { + return (values['author_id'] as int); + } + + set authorId(int value) => values['author_id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Book model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + if (model.partnerAuthor != null) { + values['partner_author_id'] = model.partnerAuthor?.id; + } + if (model.author != null) { + values['author_id'] = model.author?.id; + } + } +} + +class AuthorQuery extends Query { + AuthorQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AuthorQueryWhere(this); + } + + @override + final AuthorQueryValues values = AuthorQueryValues(); + + List _selectedFields = []; + + AuthorQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'authors'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'publisher', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AuthorQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AuthorQueryWhere? get where { + return _where; + } + + @override + AuthorQueryWhere newWhereClause() { + return AuthorQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Author( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + publisher: fields.contains('publisher') ? (row[4] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class AuthorQueryWhere extends QueryWhere { + AuthorQueryWhere(AuthorQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + publisher = StringSqlExpressionBuilder( + query, + 'publisher', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + final StringSqlExpressionBuilder publisher; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + publisher, + ]; + } +} + +class AuthorQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + String? get publisher { + return (values['publisher'] as String?); + } + + set publisher(String? value) => values['publisher'] = value; + + void copyFrom(Author model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + publisher = model.publisher; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Book extends _Book { + Book({ + this.id, + this.createdAt, + this.updatedAt, + this.partnerAuthor, + this.author, + this.name, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Author? partnerAuthor; + + @override + _Author? author; + + @override + String? name; + + Book copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Author? partnerAuthor, + _Author? author, + String? name, + }) { + return Book( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + partnerAuthor: partnerAuthor ?? this.partnerAuthor, + author: author ?? this.author, + name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Book && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.partnerAuthor == partnerAuthor && + other.author == author && + other.name == name; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + partnerAuthor, + author, + name, + ]); + } + + @override + String toString() { + return 'Book(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, partnerAuthor=$partnerAuthor, author=$author, name=$name)'; + } + + Map toJson() { + return BookSerializer.toMap(this); + } +} + +@generatedSerializable +class Author extends _Author { + Author({ + this.id, + this.createdAt, + this.updatedAt, + this.name = 'Tobe Osakwe', + this.publisher, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + String? publisher; + + Author copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + String? publisher, + }) { + return Author( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + publisher: publisher ?? this.publisher); + } + + @override + bool operator ==(other) { + return other is _Author && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + other.publisher == publisher; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + publisher, + ]); + } + + @override + String toString() { + return 'Author(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, publisher=$publisher)'; + } + + Map toJson() { + return AuthorSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const BookSerializer bookSerializer = BookSerializer(); + +class BookEncoder extends Converter { + const BookEncoder(); + + @override + Map convert(Book model) => BookSerializer.toMap(model); +} + +class BookDecoder extends Converter { + const BookDecoder(); + + @override + Book convert(Map map) => BookSerializer.fromMap(map); +} + +class BookSerializer extends Codec { + const BookSerializer(); + + @override + BookEncoder get encoder => const BookEncoder(); + + @override + BookDecoder get decoder => const BookDecoder(); + + static Book fromMap(Map map) { + return Book( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + partnerAuthor: map['partner_author'] != null + ? AuthorSerializer.fromMap(map['partner_author'] as Map) + : null, + author: map['author'] != null + ? AuthorSerializer.fromMap(map['author'] as Map) + : null, + name: map['name'] as String?); + } + + static Map toMap(_Book? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'partner_author': AuthorSerializer.toMap(model.partnerAuthor), + 'author': AuthorSerializer.toMap(model.author), + 'name': model.name + }; + } +} + +abstract class BookFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + partnerAuthor, + author, + name, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String partnerAuthor = 'partner_author'; + + static const String author = 'author'; + + static const String name = 'name'; +} + +const AuthorSerializer authorSerializer = AuthorSerializer(); + +class AuthorEncoder extends Converter { + const AuthorEncoder(); + + @override + Map convert(Author model) => AuthorSerializer.toMap(model); +} + +class AuthorDecoder extends Converter { + const AuthorDecoder(); + + @override + Author convert(Map map) => AuthorSerializer.fromMap(map); +} + +class AuthorSerializer extends Codec { + const AuthorSerializer(); + + @override + AuthorEncoder get encoder => const AuthorEncoder(); + + @override + AuthorDecoder get decoder => const AuthorDecoder(); + + static Author fromMap(Map map) { + return Author( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String? ?? 'Tobe Osakwe', + publisher: map['publisher'] as String?); + } + + static Map toMap(_Author? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'publisher': model.publisher + }; + } +} + +abstract class AuthorFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + publisher, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String publisher = 'publisher'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/car.dart b/packages/orm/angel_orm_postgres/test/models/car.dart new file mode 100644 index 000000000..49c3ba97e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/car.dart @@ -0,0 +1,17 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; +part 'car.g.dart'; + +@serializable +@orm +class _Car extends Model { + String? make; + String? description; + bool? familyFriendly; + DateTime? recalledAt; + double? price; +} diff --git a/packages/orm/angel_orm_postgres/test/models/car.g.dart b/packages/orm/angel_orm_postgres/test/models/car.g.dart new file mode 100644 index 000000000..18fbb04e9 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/car.g.dart @@ -0,0 +1,450 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'car.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class CarMigration extends Migration { + @override + void up(Schema schema) { + schema.create('cars', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('make', length: 255); + table.varChar('description', length: 255); + table.boolean('family_friendly'); + table.timeStamp('recalled_at'); + table.double('price'); + }); + } + + @override + void down(Schema schema) { + schema.drop('cars'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class CarQuery extends Query { + CarQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = CarQueryWhere(this); + } + + @override + final CarQueryValues values = CarQueryValues(); + + List _selectedFields = []; + + CarQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'cars'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'make', + 'description', + 'family_friendly', + 'recalled_at', + 'price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + CarQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + CarQueryWhere? get where { + return _where; + } + + @override + CarQueryWhere newWhereClause() { + return CarQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Car( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + make: fields.contains('make') ? (row[3] as String?) : null, + description: fields.contains('description') ? (row[4] as String?) : null, + familyFriendly: + fields.contains('family_friendly') ? mapToBool(row[5]) : null, + recalledAt: + fields.contains('recalled_at') ? mapToNullableDateTime(row[6]) : null, + price: fields.contains('price') ? mapToDouble(row[7]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class CarQueryWhere extends QueryWhere { + CarQueryWhere(CarQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + make = StringSqlExpressionBuilder( + query, + 'make', + ), + description = StringSqlExpressionBuilder( + query, + 'description', + ), + familyFriendly = BooleanSqlExpressionBuilder( + query, + 'family_friendly', + ), + recalledAt = DateTimeSqlExpressionBuilder( + query, + 'recalled_at', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder make; + + final StringSqlExpressionBuilder description; + + final BooleanSqlExpressionBuilder familyFriendly; + + final DateTimeSqlExpressionBuilder recalledAt; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]; + } +} + +class CarQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get make { + return (values['make'] as String?); + } + + set make(String? value) => values['make'] = value; + + String? get description { + return (values['description'] as String?); + } + + set description(String? value) => values['description'] = value; + + bool? get familyFriendly { + return (values['family_friendly'] as bool?); + } + + set familyFriendly(bool? value) => values['family_friendly'] = value; + + DateTime? get recalledAt { + return (values['recalled_at'] as DateTime?); + } + + set recalledAt(DateTime? value) => values['recalled_at'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + void copyFrom(Car model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + make = model.make; + description = model.description; + familyFriendly = model.familyFriendly; + recalledAt = model.recalledAt; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Car extends _Car { + Car({ + this.id, + this.createdAt, + this.updatedAt, + this.make, + this.description, + this.familyFriendly, + this.recalledAt, + this.price, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? make; + + @override + String? description; + + @override + bool? familyFriendly; + + @override + DateTime? recalledAt; + + @override + double? price; + + Car copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? make, + String? description, + bool? familyFriendly, + DateTime? recalledAt, + double? price, + }) { + return Car( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + make: make ?? this.make, + description: description ?? this.description, + familyFriendly: familyFriendly ?? this.familyFriendly, + recalledAt: recalledAt ?? this.recalledAt, + price: price ?? this.price); + } + + @override + bool operator ==(other) { + return other is _Car && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.make == make && + other.description == description && + other.familyFriendly == familyFriendly && + other.recalledAt == recalledAt && + other.price == price; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]); + } + + @override + String toString() { + return 'Car(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, make=$make, description=$description, familyFriendly=$familyFriendly, recalledAt=$recalledAt, price=$price)'; + } + + Map toJson() { + return CarSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const CarSerializer carSerializer = CarSerializer(); + +class CarEncoder extends Converter { + const CarEncoder(); + + @override + Map convert(Car model) => CarSerializer.toMap(model); +} + +class CarDecoder extends Converter { + const CarDecoder(); + + @override + Car convert(Map map) => CarSerializer.fromMap(map); +} + +class CarSerializer extends Codec { + const CarSerializer(); + + @override + CarEncoder get encoder => const CarEncoder(); + + @override + CarDecoder get decoder => const CarDecoder(); + + static Car fromMap(Map map) { + return Car( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + make: map['make'] as String?, + description: map['description'] as String?, + familyFriendly: map['family_friendly'] as bool?, + recalledAt: map['recalled_at'] != null + ? (map['recalled_at'] is DateTime + ? (map['recalled_at'] as DateTime) + : DateTime.parse(map['recalled_at'].toString())) + : null, + price: map['price'] as double?); + } + + static Map toMap(_Car? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'make': model.make, + 'description': model.description, + 'family_friendly': model.familyFriendly, + 'recalled_at': model.recalledAt?.toIso8601String(), + 'price': model.price + }; + } +} + +abstract class CarFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + make, + description, + familyFriendly, + recalledAt, + price, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String make = 'make'; + + static const String description = 'description'; + + static const String familyFriendly = 'family_friendly'; + + static const String recalledAt = 'recalled_at'; + + static const String price = 'price'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/custom_expr.dart b/packages/orm/angel_orm_postgres/test/models/custom_expr.dart new file mode 100644 index 000000000..beef30a3e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/custom_expr.dart @@ -0,0 +1,22 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'custom_expr.g.dart'; + +@serializable +@orm +class _Numbers extends Model { + @Column(expression: 'SELECT 2') + int? two; +} + +@serializable +@orm +class _Alphabet extends Model { + String? value; + + @belongsTo + _Numbers? numbers; +} diff --git a/packages/orm/angel_orm_postgres/test/models/custom_expr.g.dart b/packages/orm/angel_orm_postgres/test/models/custom_expr.g.dart new file mode 100644 index 000000000..023c010f2 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/custom_expr.g.dart @@ -0,0 +1,677 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'custom_expr.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class NumbersMigration extends Migration { + @override + void up(Schema schema) { + schema.create('numbers', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + }); + } + + @override + void down(Schema schema) { + schema.drop('numbers'); + } +} + +class AlphabetMigration extends Migration { + @override + void up(Schema schema) { + schema.create('alphabets', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('value', length: 255); + table + .declare('numbers_id', ColumnType('int')) + .references('numbers', 'id'); + }); + } + + @override + void down(Schema schema) { + schema.drop('alphabets'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class NumbersQuery extends Query { + NumbersQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['two'] = 'SELECT 2'; + _where = NumbersQueryWhere(this); + } + + @override + final NumbersQueryValues values = NumbersQueryValues(); + + List _selectedFields = []; + + NumbersQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'numbers'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'two', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + NumbersQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + NumbersQueryWhere? get where { + return _where; + } + + @override + NumbersQueryWhere newWhereClause() { + return NumbersQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Numbers( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + two: fields.contains('two') ? (row[3] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class NumbersQueryWhere extends QueryWhere { + NumbersQueryWhere(NumbersQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + ]; + } +} + +class NumbersQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + void copyFrom(Numbers model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + } +} + +class AlphabetQuery extends Query { + AlphabetQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = AlphabetQueryWhere(this); + leftJoin( + _numbers = NumbersQuery( + trampoline: trampoline, + parent: this, + ), + 'numbers_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'two', + ], + trampoline: trampoline, + ); + } + + @override + final AlphabetQueryValues values = AlphabetQueryValues(); + + List _selectedFields = []; + + AlphabetQueryWhere? _where; + + late NumbersQuery _numbers; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'alphabets'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'value', + 'numbers_id', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + AlphabetQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + AlphabetQueryWhere? get where { + return _where; + } + + @override + AlphabetQueryWhere newWhereClause() { + return AlphabetQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Alphabet( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + value: fields.contains('value') ? (row[3] as String?) : null, + ); + if (row.length > 5) { + var modelOpt = NumbersQuery().parseRow(row.skip(5).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(numbers: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + NumbersQuery get numbers { + return _numbers; + } +} + +class AlphabetQueryWhere extends QueryWhere { + AlphabetQueryWhere(AlphabetQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + value = StringSqlExpressionBuilder( + query, + 'value', + ), + numbersId = NumericSqlExpressionBuilder( + query, + 'numbers_id', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder value; + + final NumericSqlExpressionBuilder numbersId; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + value, + numbersId, + ]; + } +} + +class AlphabetQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get value { + return (values['value'] as String?); + } + + set value(String? value) => values['value'] = value; + + int get numbersId { + return (values['numbers_id'] as int); + } + + set numbersId(int value) => values['numbers_id'] = value; + + void copyFrom(Alphabet model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + value = model.value; + if (model.numbers != null) { + values['numbers_id'] = model.numbers?.id; + } + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Numbers extends _Numbers { + Numbers({ + this.id, + this.createdAt, + this.updatedAt, + this.two, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? two; + + Numbers copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? two, + }) { + return Numbers( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + two: two ?? this.two); + } + + @override + bool operator ==(other) { + return other is _Numbers && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.two == two; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + two, + ]); + } + + @override + String toString() { + return 'Numbers(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, two=$two)'; + } + + Map toJson() { + return NumbersSerializer.toMap(this); + } +} + +@generatedSerializable +class Alphabet extends _Alphabet { + Alphabet({ + this.id, + this.createdAt, + this.updatedAt, + this.value, + this.numbers, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? value; + + @override + _Numbers? numbers; + + Alphabet copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? value, + _Numbers? numbers, + }) { + return Alphabet( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + value: value ?? this.value, + numbers: numbers ?? this.numbers); + } + + @override + bool operator ==(other) { + return other is _Alphabet && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.value == value && + other.numbers == numbers; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + value, + numbers, + ]); + } + + @override + String toString() { + return 'Alphabet(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, value=$value, numbers=$numbers)'; + } + + Map toJson() { + return AlphabetSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const NumbersSerializer numbersSerializer = NumbersSerializer(); + +class NumbersEncoder extends Converter { + const NumbersEncoder(); + + @override + Map convert(Numbers model) => NumbersSerializer.toMap(model); +} + +class NumbersDecoder extends Converter { + const NumbersDecoder(); + + @override + Numbers convert(Map map) => NumbersSerializer.fromMap(map); +} + +class NumbersSerializer extends Codec { + const NumbersSerializer(); + + @override + NumbersEncoder get encoder => const NumbersEncoder(); + + @override + NumbersDecoder get decoder => const NumbersDecoder(); + + static Numbers fromMap(Map map) { + return Numbers( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + two: map['two'] as int?); + } + + static Map toMap(_Numbers? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'two': model.two + }; + } +} + +abstract class NumbersFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + two, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String two = 'two'; +} + +const AlphabetSerializer alphabetSerializer = AlphabetSerializer(); + +class AlphabetEncoder extends Converter { + const AlphabetEncoder(); + + @override + Map convert(Alphabet model) => AlphabetSerializer.toMap(model); +} + +class AlphabetDecoder extends Converter { + const AlphabetDecoder(); + + @override + Alphabet convert(Map map) => AlphabetSerializer.fromMap(map); +} + +class AlphabetSerializer extends Codec { + const AlphabetSerializer(); + + @override + AlphabetEncoder get encoder => const AlphabetEncoder(); + + @override + AlphabetDecoder get decoder => const AlphabetDecoder(); + + static Alphabet fromMap(Map map) { + return Alphabet( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + value: map['value'] as String?, + numbers: map['numbers'] != null + ? NumbersSerializer.fromMap(map['numbers'] as Map) + : null); + } + + static Map toMap(_Alphabet? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'value': model.value, + 'numbers': NumbersSerializer.toMap(model.numbers) + }; + } +} + +abstract class AlphabetFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + value, + numbers, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String value = 'value'; + + static const String numbers = 'numbers'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/email_indexed.dart b/packages/orm/angel_orm_postgres/test/models/email_indexed.dart new file mode 100644 index 000000000..928bb7f48 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/email_indexed.dart @@ -0,0 +1,41 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'email_indexed.g.dart'; + +// * https://github.com/angel-dart/angel/issues/116 + +@serializable +@orm +abstract class _Role { + @PrimaryKey(columnType: ColumnType.varChar) + String? get role; + + @ManyToMany(_RoleUser) + List<_User> get users; +} + +@serializable +@orm +abstract class _RoleUser { + @belongsTo + _Role? get role; + + @belongsTo + _User? get user; +} + +@serializable +@orm +abstract class _User { + // @PrimaryKey(columnType: ColumnType.varChar) + @primaryKey + String? get email; + String? get name; + String? get password; + + @ManyToMany(_RoleUser) + List<_Role> get roles; +} diff --git a/packages/orm/angel_orm_postgres/test/models/email_indexed.g.dart b/packages/orm/angel_orm_postgres/test/models/email_indexed.g.dart new file mode 100644 index 000000000..36dee3ae5 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/email_indexed.g.dart @@ -0,0 +1,948 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'email_indexed.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class RoleMigration extends Migration { + @override + void up(Schema schema) { + schema.create('roles', (table) { + table.varChar('role', length: 255).primaryKey(); + }); + } + + @override + void down(Schema schema) { + schema.drop('roles', cascade: true); + } +} + +class RoleUserMigration extends Migration { + @override + void up(Schema schema) { + schema.create('role_users', (table) { + table + .declare('role_role', ColumnType('varchar')) + .references('roles', 'role'); + table + .declare('user_email', ColumnType('varchar')) + .references('users', 'email'); + }); + } + + @override + void down(Schema schema) { + schema.drop('role_users'); + } +} + +class UserMigration extends Migration { + @override + void up(Schema schema) { + schema.create('users', (table) { + table.varChar('email', length: 255).primaryKey(); + table.varChar('name', length: 255); + table.varChar('password', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('users', cascade: true); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class RoleQuery extends Query { + RoleQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleQueryWhere(this); + leftJoin( + '(SELECT role_users.role_role, users.email, users.name, users.password FROM users LEFT JOIN role_users ON role_users.user_email=users.email)', + 'role', + 'role_role', + additionalFields: const [ + 'email', + 'name', + 'password', + ], + trampoline: trampoline, + ); + } + + @override + final RoleQueryValues values = RoleQueryValues(); + + List _selectedFields = []; + + RoleQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'roles'; + } + + @override + List get fields { + const _fields = ['role']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleQueryWhere? get where { + return _where; + } + + @override + RoleQueryWhere newWhereClause() { + return RoleQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = + Role(role: fields.contains('role') ? (row[0] as String?) : null); + if (row.length > 1) { + var modelOpt = UserQuery().parseRow(row.skip(1).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(users: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('roles') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.role == model.role); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } +} + +class RoleQueryWhere extends QueryWhere { + RoleQueryWhere(RoleQuery query) + : role = StringSqlExpressionBuilder( + query, + 'role', + ); + + final StringSqlExpressionBuilder role; + + @override + List get expressionBuilders { + return [role]; + } +} + +class RoleQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get role { + return (values['role'] as String?); + } + + set role(String? value) => values['role'] = value; + + void copyFrom(Role model) { + role = model.role; + } +} + +class RoleUserQuery extends Query { + RoleUserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleUserQueryWhere(this); + leftJoin( + _role = RoleQuery( + trampoline: trampoline, + parent: this, + ), + 'role_role', + 'role', + additionalFields: const ['role'], + trampoline: trampoline, + ); + leftJoin( + _user = UserQuery( + trampoline: trampoline, + parent: this, + ), + 'user_email', + 'email', + additionalFields: const [ + 'email', + 'name', + 'password', + ], + trampoline: trampoline, + ); + } + + @override + final RoleUserQueryValues values = RoleUserQueryValues(); + + List _selectedFields = []; + + RoleUserQueryWhere? _where; + + late RoleQuery _role; + + late UserQuery _user; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'role_users'; + } + + @override + List get fields { + const _fields = [ + 'role_role', + 'user_email', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleUserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleUserQueryWhere? get where { + return _where; + } + + @override + RoleUserQueryWhere newWhereClause() { + return RoleUserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = RoleUser(); + if (row.length > 2) { + var modelOpt = RoleQuery().parseRow(row.skip(2).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(role: m); + }); + } + if (row.length > 3) { + var modelOpt = UserQuery().parseRow(row.skip(3).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(user: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + RoleQuery get role { + return _role; + } + + UserQuery get user { + return _user; + } +} + +class RoleUserQueryWhere extends QueryWhere { + RoleUserQueryWhere(RoleUserQuery query) + : roleRole = StringSqlExpressionBuilder( + query, + 'role_role', + ), + userEmail = StringSqlExpressionBuilder( + query, + 'user_email', + ); + + final StringSqlExpressionBuilder roleRole; + + final StringSqlExpressionBuilder userEmail; + + @override + List get expressionBuilders { + return [ + roleRole, + userEmail, + ]; + } +} + +class RoleUserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get roleRole { + return (values['role_role'] as String?); + } + + set roleRole(String? value) => values['role_role'] = value; + + String? get userEmail { + return (values['user_email'] as String?); + } + + set userEmail(String? value) => values['user_email'] = value; + + void copyFrom(RoleUser model) { + if (model.role != null) { + values['role_role'] = model.role?.role; + } + if (model.user != null) { + values['user_email'] = model.user?.email; + } + } +} + +class UserQuery extends Query { + UserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UserQueryWhere(this); + leftJoin( + '(SELECT role_users.user_email, roles.role FROM roles LEFT JOIN role_users ON role_users.role_role=roles.role)', + 'email', + 'user_email', + additionalFields: const ['role'], + trampoline: trampoline, + ); + } + + @override + final UserQueryValues values = UserQueryValues(); + + List _selectedFields = []; + + UserQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'users'; + } + + @override + List get fields { + const _fields = [ + 'email', + 'name', + 'password', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UserQueryWhere? get where { + return _where; + } + + @override + UserQueryWhere newWhereClause() { + return UserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = User( + email: fields.contains('email') ? (row[0] as String?) : null, + name: fields.contains('name') ? (row[1] as String?) : null, + password: fields.contains('password') ? (row[2] as String?) : null, + ); + if (row.length > 3) { + var modelOpt = RoleQuery().parseRow(row.skip(3).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(roles: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('users') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.email == model.email); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } +} + +class UserQueryWhere extends QueryWhere { + UserQueryWhere(UserQuery query) + : email = StringSqlExpressionBuilder( + query, + 'email', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + password = StringSqlExpressionBuilder( + query, + 'password', + ); + + final StringSqlExpressionBuilder email; + + final StringSqlExpressionBuilder name; + + final StringSqlExpressionBuilder password; + + @override + List get expressionBuilders { + return [ + email, + name, + password, + ]; + } +} + +class UserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get email { + return (values['email'] as String?); + } + + set email(String? value) => values['email'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + String? get password { + return (values['password'] as String?); + } + + set password(String? value) => values['password'] = value; + + void copyFrom(User model) { + email = model.email; + name = model.name; + password = model.password; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Role implements _Role { + Role({ + this.role, + this.users = const [], + }); + + @override + String? role; + + @override + List<_User> users; + + Role copyWith({ + String? role, + List<_User>? users, + }) { + return Role(role: role ?? this.role, users: users ?? this.users); + } + + @override + bool operator ==(other) { + return other is _Role && + other.role == role && + ListEquality<_User>(DefaultEquality<_User>()) + .equals(other.users, users); + } + + @override + int get hashCode { + return hashObjects([ + role, + users, + ]); + } + + @override + String toString() { + return 'Role(role=$role, users=$users)'; + } + + Map toJson() { + return RoleSerializer.toMap(this); + } +} + +@generatedSerializable +class RoleUser implements _RoleUser { + RoleUser({ + this.role, + this.user, + }); + + @override + _Role? role; + + @override + _User? user; + + RoleUser copyWith({ + _Role? role, + _User? user, + }) { + return RoleUser(role: role ?? this.role, user: user ?? this.user); + } + + @override + bool operator ==(other) { + return other is _RoleUser && other.role == role && other.user == user; + } + + @override + int get hashCode { + return hashObjects([ + role, + user, + ]); + } + + @override + String toString() { + return 'RoleUser(role=$role, user=$user)'; + } + + Map toJson() { + return RoleUserSerializer.toMap(this); + } +} + +@generatedSerializable +class User implements _User { + User({ + this.email, + this.name, + this.password, + this.roles = const [], + }); + + @override + String? email; + + @override + String? name; + + @override + String? password; + + @override + List<_Role> roles; + + User copyWith({ + String? email, + String? name, + String? password, + List<_Role>? roles, + }) { + return User( + email: email ?? this.email, + name: name ?? this.name, + password: password ?? this.password, + roles: roles ?? this.roles); + } + + @override + bool operator ==(other) { + return other is _User && + other.email == email && + other.name == name && + other.password == password && + ListEquality<_Role>(DefaultEquality<_Role>()) + .equals(other.roles, roles); + } + + @override + int get hashCode { + return hashObjects([ + email, + name, + password, + roles, + ]); + } + + @override + String toString() { + return 'User(email=$email, name=$name, password=$password, roles=$roles)'; + } + + Map toJson() { + return UserSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const RoleSerializer roleSerializer = RoleSerializer(); + +class RoleEncoder extends Converter { + const RoleEncoder(); + + @override + Map convert(Role model) => RoleSerializer.toMap(model); +} + +class RoleDecoder extends Converter { + const RoleDecoder(); + + @override + Role convert(Map map) => RoleSerializer.fromMap(map); +} + +class RoleSerializer extends Codec { + const RoleSerializer(); + + @override + RoleEncoder get encoder => const RoleEncoder(); + + @override + RoleDecoder get decoder => const RoleDecoder(); + + static Role fromMap(Map map) { + return Role( + role: map['role'] as String?, + users: map['users'] is Iterable + ? List.unmodifiable(((map['users'] as Iterable).whereType()) + .map(UserSerializer.fromMap)) + : []); + } + + static Map toMap(_Role? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': model.role, + 'users': model.users.map((m) => UserSerializer.toMap(m)).toList() + }; + } +} + +abstract class RoleFields { + static const List allFields = [ + role, + users, + ]; + + static const String role = 'role'; + + static const String users = 'users'; +} + +const RoleUserSerializer roleUserSerializer = RoleUserSerializer(); + +class RoleUserEncoder extends Converter { + const RoleUserEncoder(); + + @override + Map convert(RoleUser model) => RoleUserSerializer.toMap(model); +} + +class RoleUserDecoder extends Converter { + const RoleUserDecoder(); + + @override + RoleUser convert(Map map) => RoleUserSerializer.fromMap(map); +} + +class RoleUserSerializer extends Codec { + const RoleUserSerializer(); + + @override + RoleUserEncoder get encoder => const RoleUserEncoder(); + + @override + RoleUserDecoder get decoder => const RoleUserDecoder(); + + static RoleUser fromMap(Map map) { + return RoleUser( + role: map['role'] != null + ? RoleSerializer.fromMap(map['role'] as Map) + : null, + user: map['user'] != null + ? UserSerializer.fromMap(map['user'] as Map) + : null); + } + + static Map toMap(_RoleUser? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': RoleSerializer.toMap(model.role), + 'user': UserSerializer.toMap(model.user) + }; + } +} + +abstract class RoleUserFields { + static const List allFields = [ + role, + user, + ]; + + static const String role = 'role'; + + static const String user = 'user'; +} + +const UserSerializer userSerializer = UserSerializer(); + +class UserEncoder extends Converter { + const UserEncoder(); + + @override + Map convert(User model) => UserSerializer.toMap(model); +} + +class UserDecoder extends Converter { + const UserDecoder(); + + @override + User convert(Map map) => UserSerializer.fromMap(map); +} + +class UserSerializer extends Codec { + const UserSerializer(); + + @override + UserEncoder get encoder => const UserEncoder(); + + @override + UserDecoder get decoder => const UserDecoder(); + + static User fromMap(Map map) { + return User( + email: map['email'] as String?, + name: map['name'] as String?, + password: map['password'] as String?, + roles: map['roles'] is Iterable + ? List.unmodifiable(((map['roles'] as Iterable).whereType()) + .map(RoleSerializer.fromMap)) + : []); + } + + static Map toMap(_User? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'email': model.email, + 'name': model.name, + 'password': model.password, + 'roles': model.roles.map((m) => RoleSerializer.toMap(m)).toList() + }; + } +} + +abstract class UserFields { + static const List allFields = [ + email, + name, + password, + roles, + ]; + + static const String email = 'email'; + + static const String name = 'name'; + + static const String password = 'password'; + + static const String roles = 'roles'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/fortune.dart b/packages/orm/angel_orm_postgres/test/models/fortune.dart new file mode 100644 index 000000000..6e4f49af2 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/fortune.dart @@ -0,0 +1,16 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:optional/optional.dart'; + +part 'fortune.g.dart'; + +@serializable +@Orm(tableName: 'fortune') +abstract class _Fortune { + @primaryKey + int? id; + + @Column(length: 2048) + String? message; +} diff --git a/packages/orm/angel_orm_postgres/test/models/fortune.g.dart b/packages/orm/angel_orm_postgres/test/models/fortune.g.dart new file mode 100644 index 000000000..c6f883810 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/fortune.g.dart @@ -0,0 +1,243 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fortune.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class FortuneMigration extends Migration { + @override + void up(Schema schema) { + schema.create('fortune', (table) { + table.integer('id').primaryKey(); + table.varChar('message', length: 2048); + }); + } + + @override + void down(Schema schema) { + schema.drop('fortune'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class FortuneQuery extends Query { + FortuneQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FortuneQueryWhere(this); + } + + @override + final FortuneQueryValues values = FortuneQueryValues(); + + List _selectedFields = []; + + FortuneQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'fortune'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'message', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FortuneQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FortuneQueryWhere? get where { + return _where; + } + + @override + FortuneQueryWhere newWhereClause() { + return FortuneQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Fortune( + id: fields.contains('id') ? (row[0] as int?) : null, + message: fields.contains('message') ? (row[1] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FortuneQueryWhere extends QueryWhere { + FortuneQueryWhere(FortuneQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + message = StringSqlExpressionBuilder( + query, + 'message', + ); + + final NumericSqlExpressionBuilder id; + + final StringSqlExpressionBuilder message; + + @override + List get expressionBuilders { + return [ + id, + message, + ]; + } +} + +class FortuneQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get id { + return (values['id'] as int?); + } + + set id(int? value) => values['id'] = value; + + String? get message { + return (values['message'] as String?); + } + + set message(String? value) => values['message'] = value; + + void copyFrom(Fortune model) { + id = model.id; + message = model.message; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Fortune extends _Fortune { + Fortune({ + this.id, + this.message, + }); + + @override + int? id; + + @override + String? message; + + Fortune copyWith({ + int? id, + String? message, + }) { + return Fortune(id: id ?? this.id, message: message ?? this.message); + } + + @override + bool operator ==(other) { + return other is _Fortune && other.id == id && other.message == message; + } + + @override + int get hashCode { + return hashObjects([ + id, + message, + ]); + } + + @override + String toString() { + return 'Fortune(id=$id, message=$message)'; + } + + Map toJson() { + return FortuneSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const FortuneSerializer fortuneSerializer = FortuneSerializer(); + +class FortuneEncoder extends Converter { + const FortuneEncoder(); + + @override + Map convert(Fortune model) => FortuneSerializer.toMap(model); +} + +class FortuneDecoder extends Converter { + const FortuneDecoder(); + + @override + Fortune convert(Map map) => FortuneSerializer.fromMap(map); +} + +class FortuneSerializer extends Codec { + const FortuneSerializer(); + + @override + FortuneEncoder get encoder => const FortuneEncoder(); + + @override + FortuneDecoder get decoder => const FortuneDecoder(); + + static Fortune fromMap(Map map) { + return Fortune(id: map['id'] as int?, message: map['message'] as String?); + } + + static Map toMap(_Fortune? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'message': model.message}; + } +} + +abstract class FortuneFields { + static const List allFields = [ + id, + message, + ]; + + static const String id = 'id'; + + static const String message = 'message'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/has_car.dart b/packages/orm/angel_orm_postgres/test/models/has_car.dart new file mode 100644 index 000000000..d5ea3bc3c --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/has_car.dart @@ -0,0 +1,48 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'has_car.g.dart'; + +// Map _carToMap(Car car) => car.toJson(); + +// Car _carFromMap(map) => CarSerializer.fromMap(map as Map); + +enum CarType { sedan, suv, atv } + +Color? codeToColor(String? code) => code == null + ? null + : Color.values.firstWhere((color) => color.code == code); + +String? colorToCode(Color? color) => color?.code; + +enum Color { + red('R'), + green('G'), + blue('B'); + + const Color(this.code); + + final String code; +} + +@orm +@serializable +abstract class _HasCar extends Model { + // TODO: Do this without explicit serializers + // @SerializableField( + // serializesTo: Map, serializer: #_carToMap, deserializer: #_carFromMap) + // Car get car; + + @SerializableField(isNullable: false, defaultValue: CarType.sedan) + CarType? get type; + + @SerializableField( + serializesTo: String, + serializer: #colorToCode, + deserializer: #codeToColor, + ) + @Column(type: ColumnType.varChar, length: 1) + Color? color; +} diff --git a/packages/orm/angel_orm_postgres/test/models/has_car.g.dart b/packages/orm/angel_orm_postgres/test/models/has_car.g.dart new file mode 100644 index 000000000..a8f4a1d9f --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/has_car.g.dart @@ -0,0 +1,370 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'has_car.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class HasCarMigration extends Migration { + @override + void up(Schema schema) { + schema.create('has_cars', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.declareColumn( + 'color', + Column(type: ColumnType('varchar'), length: 1), + ); + table.integer('type').defaultsTo(0); + }); + } + + @override + void down(Schema schema) { + schema.drop('has_cars'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class HasCarQuery extends Query { + HasCarQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = HasCarQueryWhere(this); + } + + @override + final HasCarQueryValues values = HasCarQueryValues(); + + List _selectedFields = []; + + HasCarQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'has_cars'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'color', + 'type', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + HasCarQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + HasCarQueryWhere? get where { + return _where; + } + + @override + HasCarQueryWhere newWhereClause() { + return HasCarQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = HasCar( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + color: fields.contains('color') + ? row[3] == null + ? null + : codeToColor((row[3] as String)) + : null, + type: fields.contains('type') + ? row[4] == null + ? null + : CarType.values[(row[4] as int)] + : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class HasCarQueryWhere extends QueryWhere { + HasCarQueryWhere(HasCarQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + color = StringSqlExpressionBuilder( + query, + 'color', + ), + type = EnumSqlExpressionBuilder( + query, + 'type', + (v) => v?.index as int, + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder color; + + final EnumSqlExpressionBuilder type; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + color, + type, + ]; + } +} + +class HasCarQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + Color? get color { + return codeToColor((values['color'] as String)); + } + + set color(Color? value) => values['color'] = colorToCode(value); + + CarType? get type { + return CarType.values[(values['type'] as int)]; + } + + set type(CarType? value) => values['type'] = value?.index; + + void copyFrom(HasCar model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + color = model.color; + type = model.type; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class HasCar extends _HasCar { + HasCar({ + this.id, + this.createdAt, + this.updatedAt, + this.color, + this.type = CarType.sedan, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + Color? color; + + @override + CarType? type; + + HasCar copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Color? color, + CarType? type, + }) { + return HasCar( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + color: color ?? this.color, + type: type ?? this.type); + } + + @override + bool operator ==(other) { + return other is _HasCar && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.color == color && + other.type == type; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + color, + type, + ]); + } + + @override + String toString() { + return 'HasCar(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, color=$color, type=$type)'; + } + + Map toJson() { + return HasCarSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const HasCarSerializer hasCarSerializer = HasCarSerializer(); + +class HasCarEncoder extends Converter { + const HasCarEncoder(); + + @override + Map convert(HasCar model) => HasCarSerializer.toMap(model); +} + +class HasCarDecoder extends Converter { + const HasCarDecoder(); + + @override + HasCar convert(Map map) => HasCarSerializer.fromMap(map); +} + +class HasCarSerializer extends Codec { + const HasCarSerializer(); + + @override + HasCarEncoder get encoder => const HasCarEncoder(); + + @override + HasCarDecoder get decoder => const HasCarDecoder(); + + static HasCar fromMap(Map map) { + if (map['type'] == null) { + throw FormatException("Missing required field 'type' on HasCar."); + } + + return HasCar( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + color: codeToColor(map['color']), + type: map['type'] as CarType? ?? CarType.sedan); + } + + static Map toMap(_HasCar? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'color': colorToCode(model.color), + 'type': model.type + }; + } +} + +abstract class HasCarFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + color, + type, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String color = 'color'; + + static const String type = 'type'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/has_map.dart b/packages/orm/angel_orm_postgres/test/models/has_map.dart new file mode 100644 index 000000000..02e6ccb59 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/has_map.dart @@ -0,0 +1,24 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'has_map.g.dart'; + +// String _boolToCustom(bool v) => v ? 'yes' : 'no'; +// bool _customToBool(v) => v == 'yes'; + +@orm +@serializable +abstract class _HasMap { + Map? get value; + + List? get list; + + // TODO: Support custom serializers + // @SerializableField( + // serializer: #_boolToCustom, + // deserializer: #_customToBool, + // serializesTo: String) + // bool get customBool; +} diff --git a/packages/orm/angel_orm_postgres/test/models/has_map.g.dart b/packages/orm/angel_orm_postgres/test/models/has_map.g.dart new file mode 100644 index 000000000..1c18e7284 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/has_map.g.dart @@ -0,0 +1,260 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'has_map.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class HasMapMigration extends Migration { + @override + void up(Schema schema) { + schema.create('has_maps', (table) { + table.declareColumn( + 'value', + Column(type: ColumnType('jsonb'), length: 255), + ); + table.declareColumn( + 'list', + Column(type: ColumnType('jsonb'), length: 255), + ); + }); + } + + @override + void down(Schema schema) { + schema.drop('has_maps'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class HasMapQuery extends Query { + HasMapQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = HasMapQueryWhere(this); + } + + @override + final HasMapQueryValues values = HasMapQueryValues(); + + List _selectedFields = []; + + HasMapQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'has_maps'; + } + + @override + List get fields { + const _fields = [ + 'value', + 'list', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + HasMapQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + HasMapQueryWhere? get where { + return _where; + } + + @override + HasMapQueryWhere newWhereClause() { + return HasMapQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = HasMap( + value: + fields.contains('value') ? (row[0] as Map?) : null, + list: fields.contains('list') ? (row[1] as List?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class HasMapQueryWhere extends QueryWhere { + HasMapQueryWhere(HasMapQuery query) + : value = MapSqlExpressionBuilder( + query, + 'value', + ), + list = ListSqlExpressionBuilder( + query, + 'list', + ); + + final MapSqlExpressionBuilder value; + + final ListSqlExpressionBuilder list; + + @override + List get expressionBuilders { + return [ + value, + list, + ]; + } +} + +class HasMapQueryValues extends MapQueryValues { + @override + Map get casts { + return {'list': 'jsonb'}; + } + + Map? get value { + return (values['value'] as Map?); + } + + set value(Map? value) => values['value'] = value; + + List? get list { + return json.decode((values['list'] as String)).cast(); + } + + set list(List? value) => values['list'] = json.encode(value); + + void copyFrom(HasMap model) { + value = model.value; + list = model.list; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class HasMap implements _HasMap { + HasMap({ + this.value, + this.list = const [], + }); + + @override + Map? value; + + @override + List? list; + + HasMap copyWith({ + Map? value, + List? list, + }) { + return HasMap(value: value ?? this.value, list: list ?? this.list); + } + + @override + bool operator ==(other) { + return other is _HasMap && + MapEquality( + keys: DefaultEquality(), values: DefaultEquality()) + .equals(other.value, value) && + ListEquality(DefaultEquality()).equals(other.list, list); + } + + @override + int get hashCode { + return hashObjects([ + value, + list, + ]); + } + + @override + String toString() { + return 'HasMap(value=$value, list=$list)'; + } + + Map toJson() { + return HasMapSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const HasMapSerializer hasMapSerializer = HasMapSerializer(); + +class HasMapEncoder extends Converter { + const HasMapEncoder(); + + @override + Map convert(HasMap model) => HasMapSerializer.toMap(model); +} + +class HasMapDecoder extends Converter { + const HasMapDecoder(); + + @override + HasMap convert(Map map) => HasMapSerializer.fromMap(map); +} + +class HasMapSerializer extends Codec { + const HasMapSerializer(); + + @override + HasMapEncoder get encoder => const HasMapEncoder(); + + @override + HasMapDecoder get decoder => const HasMapDecoder(); + + static HasMap fromMap(Map map) { + return HasMap( + value: map['value'] is Map + ? (map['value'] as Map).cast() + : {}, + list: map['list'] is Iterable + ? (map['list'] as Iterable).cast().toList() + : []); + } + + static Map toMap(_HasMap? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'value': model.value, 'list': model.list}; + } +} + +abstract class HasMapFields { + static const List allFields = [ + value, + list, + ]; + + static const String value = 'value'; + + static const String list = 'list'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/leg.dart b/packages/orm/angel_orm_postgres/test/models/leg.dart new file mode 100644 index 000000000..f1e4c5f60 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/leg.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'leg.g.dart'; + +@serializable +@orm +class _Leg extends Model { + @hasOne + _Foot? foot; + + String? name; +} + +@serializable +@Orm(tableName: 'feet') +class _Foot extends Model { + int? legId; + + double? nToes; +} diff --git a/packages/orm/angel_orm_postgres/test/models/leg.g.dart b/packages/orm/angel_orm_postgres/test/models/leg.g.dart new file mode 100644 index 000000000..98cbb1cab --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/leg.g.dart @@ -0,0 +1,702 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'leg.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class LegMigration extends Migration { + @override + void up(Schema schema) { + schema.create('legs', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('name', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('legs', cascade: true); + } +} + +class FootMigration extends Migration { + @override + void up(Schema schema) { + schema.create('feet', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('leg_id'); + table.double('n_toes'); + }); + } + + @override + void down(Schema schema) { + schema.drop('feet'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class LegQuery extends Query { + LegQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = LegQueryWhere(this); + leftJoin( + _foot = FootQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'leg_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'leg_id', + 'n_toes', + ], + trampoline: trampoline, + ); + } + + @override + final LegQueryValues values = LegQueryValues(); + + List _selectedFields = []; + + LegQueryWhere? _where; + + late FootQuery _foot; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'legs'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + LegQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + LegQueryWhere? get where { + return _where; + } + + @override + LegQueryWhere newWhereClause() { + return LegQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Leg( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + ); + if (row.length > 4) { + var modelOpt = FootQuery().parseRow(row.skip(4).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foot: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + FootQuery get foot { + return _foot; + } +} + +class LegQueryWhere extends QueryWhere { + LegQueryWhere(LegQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + ]; + } +} + +class LegQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Leg model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + } +} + +class FootQuery extends Query { + FootQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FootQueryWhere(this); + } + + @override + final FootQueryValues values = FootQueryValues(); + + List _selectedFields = []; + + FootQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'feet'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'leg_id', + 'n_toes', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FootQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FootQueryWhere? get where { + return _where; + } + + @override + FootQueryWhere newWhereClause() { + return FootQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Foot( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + legId: fields.contains('leg_id') ? (row[3] as int?) : null, + nToes: fields.contains('n_toes') ? mapToDouble(row[4]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FootQueryWhere extends QueryWhere { + FootQueryWhere(FootQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + legId = NumericSqlExpressionBuilder( + query, + 'leg_id', + ), + nToes = NumericSqlExpressionBuilder( + query, + 'n_toes', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder legId; + + final NumericSqlExpressionBuilder nToes; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + legId, + nToes, + ]; + } +} + +class FootQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get legId { + return (values['leg_id'] as int?); + } + + set legId(int? value) => values['leg_id'] = value; + + double? get nToes { + return (values['n_toes'] as double?) ?? 0.0; + } + + set nToes(double? value) => values['n_toes'] = value; + + void copyFrom(Foot model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + legId = model.legId; + nToes = model.nToes; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Leg extends _Leg { + Leg({ + this.id, + this.createdAt, + this.updatedAt, + this.foot, + this.name, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Foot? foot; + + @override + String? name; + + Leg copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Foot? foot, + String? name, + }) { + return Leg( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + foot: foot ?? this.foot, + name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Leg && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.foot == foot && + other.name == name; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + foot, + name, + ]); + } + + @override + String toString() { + return 'Leg(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, foot=$foot, name=$name)'; + } + + Map toJson() { + return LegSerializer.toMap(this); + } +} + +@generatedSerializable +class Foot extends _Foot { + Foot({ + this.id, + this.createdAt, + this.updatedAt, + this.legId, + this.nToes, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? legId; + + @override + double? nToes; + + Foot copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? legId, + double? nToes, + }) { + return Foot( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + legId: legId ?? this.legId, + nToes: nToes ?? this.nToes); + } + + @override + bool operator ==(other) { + return other is _Foot && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.legId == legId && + other.nToes == nToes; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + legId, + nToes, + ]); + } + + @override + String toString() { + return 'Foot(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, legId=$legId, nToes=$nToes)'; + } + + Map toJson() { + return FootSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const LegSerializer legSerializer = LegSerializer(); + +class LegEncoder extends Converter { + const LegEncoder(); + + @override + Map convert(Leg model) => LegSerializer.toMap(model); +} + +class LegDecoder extends Converter { + const LegDecoder(); + + @override + Leg convert(Map map) => LegSerializer.fromMap(map); +} + +class LegSerializer extends Codec { + const LegSerializer(); + + @override + LegEncoder get encoder => const LegEncoder(); + + @override + LegDecoder get decoder => const LegDecoder(); + + static Leg fromMap(Map map) { + return Leg( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + foot: map['foot'] != null + ? FootSerializer.fromMap(map['foot'] as Map) + : null, + name: map['name'] as String?); + } + + static Map toMap(_Leg? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'foot': FootSerializer.toMap(model.foot), + 'name': model.name + }; + } +} + +abstract class LegFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + foot, + name, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String foot = 'foot'; + + static const String name = 'name'; +} + +const FootSerializer footSerializer = FootSerializer(); + +class FootEncoder extends Converter { + const FootEncoder(); + + @override + Map convert(Foot model) => FootSerializer.toMap(model); +} + +class FootDecoder extends Converter { + const FootDecoder(); + + @override + Foot convert(Map map) => FootSerializer.fromMap(map); +} + +class FootSerializer extends Codec { + const FootSerializer(); + + @override + FootEncoder get encoder => const FootEncoder(); + + @override + FootDecoder get decoder => const FootDecoder(); + + static Foot fromMap(Map map) { + return Foot( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + legId: map['leg_id'] as int?, + nToes: map['n_toes'] as double?); + } + + static Map toMap(_Foot? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'leg_id': model.legId, + 'n_toes': model.nToes + }; + } +} + +abstract class FootFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + legId, + nToes, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String legId = 'leg_id'; + + static const String nToes = 'n_toes'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/order.dart b/packages/orm/angel_orm_postgres/test/models/order.dart new file mode 100644 index 000000000..595345b2b --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/order.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'order.g.dart'; + +@orm +@serializable +abstract class _Order extends Model { + @belongsTo + _Customer? get customer; + + int? get employeeId; + + DateTime? get orderDate; + + int? get shipperId; +} + +@orm +@serializable +class _Customer extends Model {} diff --git a/packages/orm/angel_orm_postgres/test/models/order.g.dart b/packages/orm/angel_orm_postgres/test/models/order.g.dart new file mode 100644 index 000000000..5b5289deb --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/order.g.dart @@ -0,0 +1,725 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'order.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class OrderMigration extends Migration { + @override + void up(Schema schema) { + schema.create('orders', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('employee_id'); + table.timeStamp('order_date'); + table.integer('shipper_id'); + table + .declare('customer_id', ColumnType('int')) + .references('customers', 'id'); + }); + } + + @override + void down(Schema schema) { + schema.drop('orders'); + } +} + +class CustomerMigration extends Migration { + @override + void up(Schema schema) { + schema.create('customers', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + }); + } + + @override + void down(Schema schema) { + schema.drop('customers'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class OrderQuery extends Query { + OrderQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = OrderQueryWhere(this); + leftJoin( + _customer = CustomerQuery( + trampoline: trampoline, + parent: this, + ), + 'customer_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + ], + trampoline: trampoline, + ); + } + + @override + final OrderQueryValues values = OrderQueryValues(); + + List _selectedFields = []; + + OrderQueryWhere? _where; + + late CustomerQuery _customer; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'orders'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'customer_id', + 'employee_id', + 'order_date', + 'shipper_id', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + OrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + OrderQueryWhere? get where { + return _where; + } + + @override + OrderQueryWhere newWhereClause() { + return OrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Order( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + employeeId: fields.contains('employee_id') ? (row[4] as int?) : null, + orderDate: + fields.contains('order_date') ? mapToNullableDateTime(row[5]) : null, + shipperId: fields.contains('shipper_id') ? (row[6] as int?) : null, + ); + if (row.length > 7) { + var modelOpt = CustomerQuery().parseRow(row.skip(7).take(3).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(customer: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + CustomerQuery get customer { + return _customer; + } +} + +class OrderQueryWhere extends QueryWhere { + OrderQueryWhere(OrderQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + customerId = NumericSqlExpressionBuilder( + query, + 'customer_id', + ), + employeeId = NumericSqlExpressionBuilder( + query, + 'employee_id', + ), + orderDate = DateTimeSqlExpressionBuilder( + query, + 'order_date', + ), + shipperId = NumericSqlExpressionBuilder( + query, + 'shipper_id', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder customerId; + + final NumericSqlExpressionBuilder employeeId; + + final DateTimeSqlExpressionBuilder orderDate; + + final NumericSqlExpressionBuilder shipperId; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + customerId, + employeeId, + orderDate, + shipperId, + ]; + } +} + +class OrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int get customerId { + return (values['customer_id'] as int); + } + + set customerId(int value) => values['customer_id'] = value; + + int? get employeeId { + return (values['employee_id'] as int?); + } + + set employeeId(int? value) => values['employee_id'] = value; + + DateTime? get orderDate { + return (values['order_date'] as DateTime?); + } + + set orderDate(DateTime? value) => values['order_date'] = value; + + int? get shipperId { + return (values['shipper_id'] as int?); + } + + set shipperId(int? value) => values['shipper_id'] = value; + + void copyFrom(Order model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + employeeId = model.employeeId; + orderDate = model.orderDate; + shipperId = model.shipperId; + if (model.customer != null) { + values['customer_id'] = model.customer?.id; + } + } +} + +class CustomerQuery extends Query { + CustomerQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = CustomerQueryWhere(this); + } + + @override + final CustomerQueryValues values = CustomerQueryValues(); + + List _selectedFields = []; + + CustomerQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'customers'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + CustomerQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + CustomerQueryWhere? get where { + return _where; + } + + @override + CustomerQueryWhere newWhereClause() { + return CustomerQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Customer( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class CustomerQueryWhere extends QueryWhere { + CustomerQueryWhere(CustomerQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + ]; + } +} + +class CustomerQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + void copyFrom(Customer model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Order extends _Order { + Order({ + this.id, + this.createdAt, + this.updatedAt, + this.customer, + this.employeeId, + this.orderDate, + this.shipperId, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + _Customer? customer; + + @override + int? employeeId; + + @override + DateTime? orderDate; + + @override + int? shipperId; + + Order copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + _Customer? customer, + int? employeeId, + DateTime? orderDate, + int? shipperId, + }) { + return Order( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + customer: customer ?? this.customer, + employeeId: employeeId ?? this.employeeId, + orderDate: orderDate ?? this.orderDate, + shipperId: shipperId ?? this.shipperId); + } + + @override + bool operator ==(other) { + return other is _Order && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.customer == customer && + other.employeeId == employeeId && + other.orderDate == orderDate && + other.shipperId == shipperId; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + customer, + employeeId, + orderDate, + shipperId, + ]); + } + + @override + String toString() { + return 'Order(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, customer=$customer, employeeId=$employeeId, orderDate=$orderDate, shipperId=$shipperId)'; + } + + Map toJson() { + return OrderSerializer.toMap(this); + } +} + +@generatedSerializable +class Customer extends _Customer { + Customer({ + this.id, + this.createdAt, + this.updatedAt, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + Customer copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return Customer( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt); + } + + @override + bool operator ==(other) { + return other is _Customer && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + ]); + } + + @override + String toString() { + return 'Customer(id=$id, createdAt=$createdAt, updatedAt=$updatedAt)'; + } + + Map toJson() { + return CustomerSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const OrderSerializer orderSerializer = OrderSerializer(); + +class OrderEncoder extends Converter { + const OrderEncoder(); + + @override + Map convert(Order model) => OrderSerializer.toMap(model); +} + +class OrderDecoder extends Converter { + const OrderDecoder(); + + @override + Order convert(Map map) => OrderSerializer.fromMap(map); +} + +class OrderSerializer extends Codec { + const OrderSerializer(); + + @override + OrderEncoder get encoder => const OrderEncoder(); + + @override + OrderDecoder get decoder => const OrderDecoder(); + + static Order fromMap(Map map) { + return Order( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + customer: map['customer'] != null + ? CustomerSerializer.fromMap(map['customer'] as Map) + : null, + employeeId: map['employee_id'] as int?, + orderDate: map['order_date'] != null + ? (map['order_date'] is DateTime + ? (map['order_date'] as DateTime) + : DateTime.parse(map['order_date'].toString())) + : null, + shipperId: map['shipper_id'] as int?); + } + + static Map toMap(_Order? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'customer': CustomerSerializer.toMap(model.customer), + 'employee_id': model.employeeId, + 'order_date': model.orderDate?.toIso8601String(), + 'shipper_id': model.shipperId + }; + } +} + +abstract class OrderFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + customer, + employeeId, + orderDate, + shipperId, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String customer = 'customer'; + + static const String employeeId = 'employee_id'; + + static const String orderDate = 'order_date'; + + static const String shipperId = 'shipper_id'; +} + +const CustomerSerializer customerSerializer = CustomerSerializer(); + +class CustomerEncoder extends Converter { + const CustomerEncoder(); + + @override + Map convert(Customer model) => CustomerSerializer.toMap(model); +} + +class CustomerDecoder extends Converter { + const CustomerDecoder(); + + @override + Customer convert(Map map) => CustomerSerializer.fromMap(map); +} + +class CustomerSerializer extends Codec { + const CustomerSerializer(); + + @override + CustomerEncoder get encoder => const CustomerEncoder(); + + @override + CustomerDecoder get decoder => const CustomerDecoder(); + + static Customer fromMap(Map map) { + return Customer( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null); + } + + static Map toMap(_Customer? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String() + }; + } +} + +abstract class CustomerFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/person.dart b/packages/orm/angel_orm_postgres/test/models/person.dart new file mode 100644 index 000000000..0f7a5ec4f --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/person.dart @@ -0,0 +1,26 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; +part 'person.g.dart'; + +@serializable +@Orm(tableName: 'persons') +class _Person extends Model { + String? name; + int? age; +} + +@serializable +@Orm(tableName: 'persons', generateMigrations: false) +class _PersonWithLastOrder { + String? name; + + @Column(expression: 'po.name') + String? lastOrderName; + + @Column(expression: 'po.price') + double? lastOrderPrice; +} diff --git a/packages/orm/angel_orm_postgres/test/models/person.g.dart b/packages/orm/angel_orm_postgres/test/models/person.g.dart new file mode 100644 index 000000000..9c622b35b --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/person.g.dart @@ -0,0 +1,580 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class PersonMigration extends Migration { + @override + void up(Schema schema) { + schema.create('persons', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('name', length: 255); + table.integer('age'); + }); + } + + @override + void down(Schema schema) { + schema.drop('persons'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class PersonQuery extends Query { + PersonQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = PersonQueryWhere(this); + } + + @override + final PersonQueryValues values = PersonQueryValues(); + + List _selectedFields = []; + + PersonQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'persons'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'age', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonQueryWhere? get where { + return _where; + } + + @override + PersonQueryWhere newWhereClause() { + return PersonQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Person( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + age: fields.contains('age') ? (row[4] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonQueryWhere extends QueryWhere { + PersonQueryWhere(PersonQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + age = NumericSqlExpressionBuilder( + query, + 'age', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder age; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + age, + ]; + } +} + +class PersonQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + int? get age { + return (values['age'] as int?); + } + + set age(int? value) => values['age'] = value; + + void copyFrom(Person model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + age = model.age; + } +} + +class PersonWithLastOrderQuery + extends Query { + PersonWithLastOrderQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['last_order_name'] = 'po.name'; + expressions['last_order_price'] = 'po.price'; + _where = PersonWithLastOrderQueryWhere(this); + } + + @override + final PersonWithLastOrderQueryValues values = + PersonWithLastOrderQueryValues(); + + List _selectedFields = []; + + PersonWithLastOrderQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'persons'; + } + + @override + List get fields { + const _fields = [ + 'name', + 'last_order_name', + 'last_order_price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonWithLastOrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonWithLastOrderQueryWhere? get where { + return _where; + } + + @override + PersonWithLastOrderQueryWhere newWhereClause() { + return PersonWithLastOrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = PersonWithLastOrder( + name: fields.contains('name') ? (row[0] as String?) : null, + lastOrderName: + fields.contains('last_order_name') ? (row[1] as String?) : null, + lastOrderPrice: + fields.contains('last_order_price') ? mapToDouble(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonWithLastOrderQueryWhere extends QueryWhere { + PersonWithLastOrderQueryWhere(PersonWithLastOrderQuery query) + : name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [name]; + } +} + +class PersonWithLastOrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(PersonWithLastOrder model) { + name = model.name; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Person extends _Person { + Person({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + this.age, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + int? age; + + Person copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + int? age, + }) { + return Person( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + age: age ?? this.age); + } + + @override + bool operator ==(other) { + return other is _Person && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + other.age == age; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + age, + ]); + } + + @override + String toString() { + return 'Person(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, age=$age)'; + } + + Map toJson() { + return PersonSerializer.toMap(this); + } +} + +@generatedSerializable +class PersonWithLastOrder extends _PersonWithLastOrder { + PersonWithLastOrder({ + this.name, + this.lastOrderName, + this.lastOrderPrice, + }); + + @override + String? name; + + @override + String? lastOrderName; + + @override + double? lastOrderPrice; + + PersonWithLastOrder copyWith({ + String? name, + String? lastOrderName, + double? lastOrderPrice, + }) { + return PersonWithLastOrder( + name: name ?? this.name, + lastOrderName: lastOrderName ?? this.lastOrderName, + lastOrderPrice: lastOrderPrice ?? this.lastOrderPrice); + } + + @override + bool operator ==(other) { + return other is _PersonWithLastOrder && + other.name == name && + other.lastOrderName == lastOrderName && + other.lastOrderPrice == lastOrderPrice; + } + + @override + int get hashCode { + return hashObjects([ + name, + lastOrderName, + lastOrderPrice, + ]); + } + + @override + String toString() { + return 'PersonWithLastOrder(name=$name, lastOrderName=$lastOrderName, lastOrderPrice=$lastOrderPrice)'; + } + + Map toJson() { + return PersonWithLastOrderSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const PersonSerializer personSerializer = PersonSerializer(); + +class PersonEncoder extends Converter { + const PersonEncoder(); + + @override + Map convert(Person model) => PersonSerializer.toMap(model); +} + +class PersonDecoder extends Converter { + const PersonDecoder(); + + @override + Person convert(Map map) => PersonSerializer.fromMap(map); +} + +class PersonSerializer extends Codec { + const PersonSerializer(); + + @override + PersonEncoder get encoder => const PersonEncoder(); + + @override + PersonDecoder get decoder => const PersonDecoder(); + + static Person fromMap(Map map) { + return Person( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + age: map['age'] as int?); + } + + static Map toMap(_Person? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'age': model.age + }; + } +} + +abstract class PersonFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + age, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String age = 'age'; +} + +const PersonWithLastOrderSerializer personWithLastOrderSerializer = + PersonWithLastOrderSerializer(); + +class PersonWithLastOrderEncoder extends Converter { + const PersonWithLastOrderEncoder(); + + @override + Map convert(PersonWithLastOrder model) => + PersonWithLastOrderSerializer.toMap(model); +} + +class PersonWithLastOrderDecoder extends Converter { + const PersonWithLastOrderDecoder(); + + @override + PersonWithLastOrder convert(Map map) => + PersonWithLastOrderSerializer.fromMap(map); +} + +class PersonWithLastOrderSerializer extends Codec { + const PersonWithLastOrderSerializer(); + + @override + PersonWithLastOrderEncoder get encoder => const PersonWithLastOrderEncoder(); + + @override + PersonWithLastOrderDecoder get decoder => const PersonWithLastOrderDecoder(); + + static PersonWithLastOrder fromMap(Map map) { + return PersonWithLastOrder( + name: map['name'] as String?, + lastOrderName: map['last_order_name'] as String?, + lastOrderPrice: map['last_order_price'] as double?); + } + + static Map toMap(_PersonWithLastOrder? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'name': model.name, + 'last_order_name': model.lastOrderName, + 'last_order_price': model.lastOrderPrice + }; + } +} + +abstract class PersonWithLastOrderFields { + static const List allFields = [ + name, + lastOrderName, + lastOrderPrice, + ]; + + static const String name = 'name'; + + static const String lastOrderName = 'last_order_name'; + + static const String lastOrderPrice = 'last_order_price'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/person_order.dart b/packages/orm/angel_orm_postgres/test/models/person_order.dart new file mode 100644 index 000000000..6794d8628 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/person_order.dart @@ -0,0 +1,36 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'person_order.g.dart'; + +@orm +@serializable +abstract class _PersonOrder extends Model { + int? get personId; + + String? get name; + + double? get price; + + bool? get deleted; +} + +@serializable +@Orm(tableName: 'person_orders', generateMigrations: false) +abstract class _OrderWithPersonInfo extends Model { + String? get name; + + double? get price; + + bool? get deleted; + + @Column(expression: 'p.name') + String? get personName; + + @Column(expression: 'p.age') + int? get personAge; +} diff --git a/packages/orm/angel_orm_postgres/test/models/person_order.g.dart b/packages/orm/angel_orm_postgres/test/models/person_order.g.dart new file mode 100644 index 000000000..a87ce207c --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/person_order.g.dart @@ -0,0 +1,797 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person_order.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class PersonOrderMigration extends Migration { + @override + void up(Schema schema) { + schema.create('person_orders', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('person_id'); + table.varChar('name', length: 255); + table.double('price'); + table.boolean('deleted'); + }); + } + + @override + void down(Schema schema) { + schema.drop('person_orders'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class PersonOrderQuery extends Query { + PersonOrderQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = PersonOrderQueryWhere(this); + } + + @override + final PersonOrderQueryValues values = PersonOrderQueryValues(); + + List _selectedFields = []; + + PersonOrderQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'person_orders'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'person_id', + 'name', + 'price', + 'deleted', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + PersonOrderQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + PersonOrderQueryWhere? get where { + return _where; + } + + @override + PersonOrderQueryWhere newWhereClause() { + return PersonOrderQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = PersonOrder( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + personId: fields.contains('person_id') ? (row[3] as int?) : null, + name: fields.contains('name') ? (row[4] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[5]) : null, + deleted: fields.contains('deleted') ? mapToBool(row[6]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class PersonOrderQueryWhere extends QueryWhere { + PersonOrderQueryWhere(PersonOrderQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + personId = NumericSqlExpressionBuilder( + query, + 'person_id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + deleted = BooleanSqlExpressionBuilder( + query, + 'deleted', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder personId; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + final BooleanSqlExpressionBuilder deleted; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]; + } +} + +class PersonOrderQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get personId { + return (values['person_id'] as int?); + } + + set personId(int? value) => values['person_id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + bool? get deleted { + return (values['deleted'] as bool?); + } + + set deleted(bool? value) => values['deleted'] = value; + + void copyFrom(PersonOrder model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + personId = model.personId; + name = model.name; + price = model.price; + deleted = model.deleted; + } +} + +class OrderWithPersonInfoQuery + extends Query { + OrderWithPersonInfoQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + expressions['person_name'] = 'p.name'; + expressions['person_age'] = 'p.age'; + _where = OrderWithPersonInfoQueryWhere(this); + } + + @override + final OrderWithPersonInfoQueryValues values = + OrderWithPersonInfoQueryValues(); + + List _selectedFields = []; + + OrderWithPersonInfoQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'person_orders'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + 'price', + 'deleted', + 'person_name', + 'person_age', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + OrderWithPersonInfoQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + OrderWithPersonInfoQueryWhere? get where { + return _where; + } + + @override + OrderWithPersonInfoQueryWhere newWhereClause() { + return OrderWithPersonInfoQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = OrderWithPersonInfo( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[4]) : null, + deleted: fields.contains('deleted') ? mapToBool(row[5]) : null, + personName: fields.contains('person_name') ? (row[6] as String?) : null, + personAge: fields.contains('person_age') ? (row[7] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class OrderWithPersonInfoQueryWhere extends QueryWhere { + OrderWithPersonInfoQueryWhere(OrderWithPersonInfoQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ), + deleted = BooleanSqlExpressionBuilder( + query, + 'deleted', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + final BooleanSqlExpressionBuilder deleted; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + price, + deleted, + ]; + } +} + +class OrderWithPersonInfoQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + bool? get deleted { + return (values['deleted'] as bool?); + } + + set deleted(bool? value) => values['deleted'] = value; + + void copyFrom(OrderWithPersonInfo model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + price = model.price; + deleted = model.deleted; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class PersonOrder extends _PersonOrder { + PersonOrder({ + this.id, + this.createdAt, + this.updatedAt, + this.personId, + this.name, + this.price, + this.deleted, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? personId; + + @override + String? name; + + @override + double? price; + + @override + bool? deleted; + + PersonOrder copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? personId, + String? name, + double? price, + bool? deleted, + }) { + return PersonOrder( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + personId: personId ?? this.personId, + name: name ?? this.name, + price: price ?? this.price, + deleted: deleted ?? this.deleted); + } + + @override + bool operator ==(other) { + return other is _PersonOrder && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.personId == personId && + other.name == name && + other.price == price && + other.deleted == deleted; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]); + } + + @override + String toString() { + return 'PersonOrder(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, personId=$personId, name=$name, price=$price, deleted=$deleted)'; + } + + Map toJson() { + return PersonOrderSerializer.toMap(this); + } +} + +@generatedSerializable +class OrderWithPersonInfo extends _OrderWithPersonInfo { + OrderWithPersonInfo({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + this.price, + this.deleted, + this.personName, + this.personAge, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + double? price; + + @override + bool? deleted; + + @override + String? personName; + + @override + int? personAge; + + OrderWithPersonInfo copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + double? price, + bool? deleted, + String? personName, + int? personAge, + }) { + return OrderWithPersonInfo( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + price: price ?? this.price, + deleted: deleted ?? this.deleted, + personName: personName ?? this.personName, + personAge: personAge ?? this.personAge); + } + + @override + bool operator ==(other) { + return other is _OrderWithPersonInfo && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + other.price == price && + other.deleted == deleted && + other.personName == personName && + other.personAge == personAge; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + price, + deleted, + personName, + personAge, + ]); + } + + @override + String toString() { + return 'OrderWithPersonInfo(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, price=$price, deleted=$deleted, personName=$personName, personAge=$personAge)'; + } + + Map toJson() { + return OrderWithPersonInfoSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const PersonOrderSerializer personOrderSerializer = PersonOrderSerializer(); + +class PersonOrderEncoder extends Converter { + const PersonOrderEncoder(); + + @override + Map convert(PersonOrder model) => PersonOrderSerializer.toMap(model); +} + +class PersonOrderDecoder extends Converter { + const PersonOrderDecoder(); + + @override + PersonOrder convert(Map map) => PersonOrderSerializer.fromMap(map); +} + +class PersonOrderSerializer extends Codec { + const PersonOrderSerializer(); + + @override + PersonOrderEncoder get encoder => const PersonOrderEncoder(); + + @override + PersonOrderDecoder get decoder => const PersonOrderDecoder(); + + static PersonOrder fromMap(Map map) { + return PersonOrder( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + personId: map['person_id'] as int?, + name: map['name'] as String?, + price: map['price'] as double?, + deleted: map['deleted'] as bool?); + } + + static Map toMap(_PersonOrder? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'person_id': model.personId, + 'name': model.name, + 'price': model.price, + 'deleted': model.deleted + }; + } +} + +abstract class PersonOrderFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + personId, + name, + price, + deleted, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String personId = 'person_id'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String deleted = 'deleted'; +} + +const OrderWithPersonInfoSerializer orderWithPersonInfoSerializer = + OrderWithPersonInfoSerializer(); + +class OrderWithPersonInfoEncoder extends Converter { + const OrderWithPersonInfoEncoder(); + + @override + Map convert(OrderWithPersonInfo model) => + OrderWithPersonInfoSerializer.toMap(model); +} + +class OrderWithPersonInfoDecoder extends Converter { + const OrderWithPersonInfoDecoder(); + + @override + OrderWithPersonInfo convert(Map map) => + OrderWithPersonInfoSerializer.fromMap(map); +} + +class OrderWithPersonInfoSerializer extends Codec { + const OrderWithPersonInfoSerializer(); + + @override + OrderWithPersonInfoEncoder get encoder => const OrderWithPersonInfoEncoder(); + + @override + OrderWithPersonInfoDecoder get decoder => const OrderWithPersonInfoDecoder(); + + static OrderWithPersonInfo fromMap(Map map) { + return OrderWithPersonInfo( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + price: map['price'] as double?, + deleted: map['deleted'] as bool?, + personName: map['person_name'] as String?, + personAge: map['person_age'] as int?); + } + + static Map toMap(_OrderWithPersonInfo? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'price': model.price, + 'deleted': model.deleted, + 'person_name': model.personName, + 'person_age': model.personAge + }; + } +} + +abstract class OrderWithPersonInfoFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + price, + deleted, + personName, + personAge, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String price = 'price'; + + static const String deleted = 'deleted'; + + static const String personName = 'person_name'; + + static const String personAge = 'person_age'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/quotation.dart b/packages/orm/angel_orm_postgres/test/models/quotation.dart new file mode 100644 index 000000000..02b721969 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/quotation.dart @@ -0,0 +1,17 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'quotation.g.dart'; + +@serializable +@orm +abstract class _Quotation { + @PrimaryKey(columnType: ColumnType.varChar) + String? get id; + + String? get name; + + double? get price; +} diff --git a/packages/orm/angel_orm_postgres/test/models/quotation.g.dart b/packages/orm/angel_orm_postgres/test/models/quotation.g.dart new file mode 100644 index 000000000..b5bbb006e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/quotation.g.dart @@ -0,0 +1,276 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quotation.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class QuotationMigration extends Migration { + @override + void up(Schema schema) { + schema.create('quotations', (table) { + table.varChar('id', length: 255).primaryKey(); + table.varChar('name', length: 255); + table.double('price'); + }); + } + + @override + void down(Schema schema) { + schema.drop('quotations'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class QuotationQuery extends Query { + QuotationQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = QuotationQueryWhere(this); + } + + @override + final QuotationQueryValues values = QuotationQueryValues(); + + List _selectedFields = []; + + QuotationQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'quotations'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'name', + 'price', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + QuotationQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + QuotationQueryWhere? get where { + return _where; + } + + @override + QuotationQueryWhere newWhereClause() { + return QuotationQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Quotation( + id: fields.contains('id') ? (row[0] as String?) : null, + name: fields.contains('name') ? (row[1] as String?) : null, + price: fields.contains('price') ? mapToDouble(row[2]) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class QuotationQueryWhere extends QueryWhere { + QuotationQueryWhere(QuotationQuery query) + : id = StringSqlExpressionBuilder( + query, + 'id', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ), + price = NumericSqlExpressionBuilder( + query, + 'price', + ); + + final StringSqlExpressionBuilder id; + + final StringSqlExpressionBuilder name; + + final NumericSqlExpressionBuilder price; + + @override + List get expressionBuilders { + return [ + id, + name, + price, + ]; + } +} + +class QuotationQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + double? get price { + return (values['price'] as double?) ?? 0.0; + } + + set price(double? value) => values['price'] = value; + + void copyFrom(Quotation model) { + id = model.id; + name = model.name; + price = model.price; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Quotation implements _Quotation { + Quotation({ + this.id, + this.name, + this.price, + }); + + @override + String? id; + + @override + String? name; + + @override + double? price; + + Quotation copyWith({ + String? id, + String? name, + double? price, + }) { + return Quotation( + id: id ?? this.id, name: name ?? this.name, price: price ?? this.price); + } + + @override + bool operator ==(other) { + return other is _Quotation && + other.id == id && + other.name == name && + other.price == price; + } + + @override + int get hashCode { + return hashObjects([ + id, + name, + price, + ]); + } + + @override + String toString() { + return 'Quotation(id=$id, name=$name, price=$price)'; + } + + Map toJson() { + return QuotationSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const QuotationSerializer quotationSerializer = QuotationSerializer(); + +class QuotationEncoder extends Converter { + const QuotationEncoder(); + + @override + Map convert(Quotation model) => QuotationSerializer.toMap(model); +} + +class QuotationDecoder extends Converter { + const QuotationDecoder(); + + @override + Quotation convert(Map map) => QuotationSerializer.fromMap(map); +} + +class QuotationSerializer extends Codec { + const QuotationSerializer(); + + @override + QuotationEncoder get encoder => const QuotationEncoder(); + + @override + QuotationDecoder get decoder => const QuotationDecoder(); + + static Quotation fromMap(Map map) { + return Quotation( + id: map['id'] as String?, + name: map['name'] as String?, + price: map['price'] as double?); + } + + static Map toMap(_Quotation? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'name': model.name, 'price': model.price}; + } +} + +abstract class QuotationFields { + static const List allFields = [ + id, + name, + price, + ]; + + static const String id = 'id'; + + static const String name = 'name'; + + static const String price = 'price'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/tree.dart b/packages/orm/angel_orm_postgres/test/models/tree.dart new file mode 100644 index 000000000..dee7eb0d9 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/tree.dart @@ -0,0 +1,25 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'tree.g.dart'; + +@serializable +@orm +class _Tree extends Model { + @Column(indexType: IndexType.unique, type: ColumnType.smallInt) + int? rings; + + @hasMany + List<_Fruit> fruits = []; +} + +@serializable +@orm +class _Fruit extends Model { + int? treeId; + String? commonName; +} diff --git a/packages/orm/angel_orm_postgres/test/models/tree.g.dart b/packages/orm/angel_orm_postgres/test/models/tree.g.dart new file mode 100644 index 000000000..1e6e0b77d --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/tree.g.dart @@ -0,0 +1,758 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tree.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class TreeMigration extends Migration { + @override + void up(Schema schema) { + schema.create('trees', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('rings').unique(); + }); + } + + @override + void down(Schema schema) { + schema.drop('trees', cascade: true); + } +} + +class FruitMigration extends Migration { + @override + void up(Schema schema) { + schema.create('fruits', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('tree_id'); + table.varChar('common_name', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('fruits'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class TreeQuery extends Query { + TreeQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = TreeQueryWhere(this); + leftJoin( + _fruits = FruitQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'tree_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'tree_id', + 'common_name', + ], + trampoline: trampoline, + ); + } + + @override + final TreeQueryValues values = TreeQueryValues(); + + List _selectedFields = []; + + TreeQueryWhere? _where; + + late FruitQuery _fruits; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'trees'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'rings', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + TreeQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + TreeQueryWhere? get where { + return _where; + } + + @override + TreeQueryWhere newWhereClause() { + return TreeQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Tree( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + rings: fields.contains('rings') ? (row[3] as int?) : null, + ); + if (row.length > 4) { + var modelOpt = FruitQuery().parseRow(row.skip(4).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(fruits: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + FruitQuery get fruits { + return _fruits; + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + fruits: List<_Fruit>.from(l.fruits)..addAll(model.fruits)); + } + }); + }); + } +} + +class TreeQueryWhere extends QueryWhere { + TreeQueryWhere(TreeQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + rings = NumericSqlExpressionBuilder( + query, + 'rings', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder rings; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + rings, + ]; + } +} + +class TreeQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get rings { + return (values['rings'] as int?); + } + + set rings(int? value) => values['rings'] = value; + + void copyFrom(Tree model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + rings = model.rings; + } +} + +class FruitQuery extends Query { + FruitQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FruitQueryWhere(this); + } + + @override + final FruitQueryValues values = FruitQueryValues(); + + List _selectedFields = []; + + FruitQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'fruits'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'tree_id', + 'common_name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FruitQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FruitQueryWhere? get where { + return _where; + } + + @override + FruitQueryWhere newWhereClause() { + return FruitQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Fruit( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + treeId: fields.contains('tree_id') ? (row[3] as int?) : null, + commonName: fields.contains('common_name') ? (row[4] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class FruitQueryWhere extends QueryWhere { + FruitQueryWhere(FruitQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + treeId = NumericSqlExpressionBuilder( + query, + 'tree_id', + ), + commonName = StringSqlExpressionBuilder( + query, + 'common_name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder treeId; + + final StringSqlExpressionBuilder commonName; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]; + } +} + +class FruitQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get treeId { + return (values['tree_id'] as int?); + } + + set treeId(int? value) => values['tree_id'] = value; + + String? get commonName { + return (values['common_name'] as String?); + } + + set commonName(String? value) => values['common_name'] = value; + + void copyFrom(Fruit model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + treeId = model.treeId; + commonName = model.commonName; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Tree extends _Tree { + Tree({ + this.id, + this.createdAt, + this.updatedAt, + this.rings, + List<_Fruit> fruits = const [], + }) : fruits = List.unmodifiable(fruits); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? rings; + + @override + List<_Fruit> fruits; + + Tree copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? rings, + List<_Fruit>? fruits, + }) { + return Tree( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + rings: rings ?? this.rings, + fruits: fruits ?? this.fruits); + } + + @override + bool operator ==(other) { + return other is _Tree && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.rings == rings && + ListEquality<_Fruit>(DefaultEquality<_Fruit>()) + .equals(other.fruits, fruits); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + rings, + fruits, + ]); + } + + @override + String toString() { + return 'Tree(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, rings=$rings, fruits=$fruits)'; + } + + Map toJson() { + return TreeSerializer.toMap(this); + } +} + +@generatedSerializable +class Fruit extends _Fruit { + Fruit({ + this.id, + this.createdAt, + this.updatedAt, + this.treeId, + this.commonName, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? treeId; + + @override + String? commonName; + + Fruit copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? treeId, + String? commonName, + }) { + return Fruit( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + treeId: treeId ?? this.treeId, + commonName: commonName ?? this.commonName); + } + + @override + bool operator ==(other) { + return other is _Fruit && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.treeId == treeId && + other.commonName == commonName; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]); + } + + @override + String toString() { + return 'Fruit(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, treeId=$treeId, commonName=$commonName)'; + } + + Map toJson() { + return FruitSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const TreeSerializer treeSerializer = TreeSerializer(); + +class TreeEncoder extends Converter { + const TreeEncoder(); + + @override + Map convert(Tree model) => TreeSerializer.toMap(model); +} + +class TreeDecoder extends Converter { + const TreeDecoder(); + + @override + Tree convert(Map map) => TreeSerializer.fromMap(map); +} + +class TreeSerializer extends Codec { + const TreeSerializer(); + + @override + TreeEncoder get encoder => const TreeEncoder(); + + @override + TreeDecoder get decoder => const TreeDecoder(); + + static Tree fromMap(Map map) { + return Tree( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + rings: map['rings'] as int?, + fruits: map['fruits'] is Iterable + ? List.unmodifiable(((map['fruits'] as Iterable).whereType()) + .map(FruitSerializer.fromMap)) + : []); + } + + static Map toMap(_Tree? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'rings': model.rings, + 'fruits': model.fruits.map((m) => FruitSerializer.toMap(m)).toList() + }; + } +} + +abstract class TreeFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + rings, + fruits, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String rings = 'rings'; + + static const String fruits = 'fruits'; +} + +const FruitSerializer fruitSerializer = FruitSerializer(); + +class FruitEncoder extends Converter { + const FruitEncoder(); + + @override + Map convert(Fruit model) => FruitSerializer.toMap(model); +} + +class FruitDecoder extends Converter { + const FruitDecoder(); + + @override + Fruit convert(Map map) => FruitSerializer.fromMap(map); +} + +class FruitSerializer extends Codec { + const FruitSerializer(); + + @override + FruitEncoder get encoder => const FruitEncoder(); + + @override + FruitDecoder get decoder => const FruitDecoder(); + + static Fruit fromMap(Map map) { + return Fruit( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + treeId: map['tree_id'] as int?, + commonName: map['common_name'] as String?); + } + + static Map toMap(_Fruit? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'tree_id': model.treeId, + 'common_name': model.commonName + }; + } +} + +abstract class FruitFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + treeId, + commonName, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String treeId = 'tree_id'; + + static const String commonName = 'common_name'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/unorthodox.dart b/packages/orm/angel_orm_postgres/test/models/unorthodox.dart new file mode 100644 index 000000000..67301f5ea --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/unorthodox.dart @@ -0,0 +1,72 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'unorthodox.g.dart'; + +@serializable +@orm +abstract class _Unorthodox { + @Column(indexType: IndexType.primaryKey) + String? get name; +} + +@serializable +@orm +abstract class _WeirdJoin { + @primaryKey + int get id; + + @BelongsTo(localKey: 'join_name', foreignKey: 'name') + _Unorthodox? get unorthodox; + + @hasOne + _Song? get song; + + @HasMany(foreignKey: 'parent') + List<_Numba> get numbas; + + @ManyToMany(_FooPivot) + List<_Foo> get foos; +} + +@serializable +@orm +abstract class _Song extends Model { + int? get weirdJoinId; + + String? get title; +} + +@serializable +@orm +class _Numba implements Comparable<_Numba> { + @primaryKey + int i = -1; + + int? parent; + + @override + int compareTo(_Numba other) => i.compareTo(other.i); +} + +@serializable +@orm +abstract class _Foo { + @primaryKey + String? get bar; + + @ManyToMany(_FooPivot) + List<_WeirdJoin> get weirdJoins; +} + +@serializable +@orm +abstract class _FooPivot { + @belongsTo + _WeirdJoin? get weirdJoin; + + @belongsTo + _Foo? get foo; +} diff --git a/packages/orm/angel_orm_postgres/test/models/unorthodox.g.dart b/packages/orm/angel_orm_postgres/test/models/unorthodox.g.dart new file mode 100644 index 000000000..35c18d1e1 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/unorthodox.g.dart @@ -0,0 +1,1777 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'unorthodox.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class UnorthodoxMigration extends Migration { + @override + void up(Schema schema) { + schema.create('unorthodoxes', (table) { + table.varChar('name', length: 255).primaryKey(); + }); + } + + @override + void down(Schema schema) { + schema.drop('unorthodoxes'); + } +} + +class WeirdJoinMigration extends Migration { + @override + void up(Schema schema) { + schema.create('weird_joins', (table) { + table.integer('id').primaryKey(); + table + .declare('join_name', ColumnType('varchar')) + .references('unorthodoxes', 'name'); + }); + } + + @override + void down(Schema schema) { + schema.drop('weird_joins', cascade: true); + } +} + +class SongMigration extends Migration { + @override + void up(Schema schema) { + schema.create('songs', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.integer('weird_join_id'); + table.varChar('title', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('songs'); + } +} + +class NumbaMigration extends Migration { + @override + void up(Schema schema) { + schema.create('numbas', (table) { + table.integer('i').primaryKey(); + table.integer('parent'); + }); + } + + @override + void down(Schema schema) { + schema.drop('numbas'); + } +} + +class FooMigration extends Migration { + @override + void up(Schema schema) { + schema.create('foos', (table) { + table.varChar('bar', length: 255).primaryKey(); + }); + } + + @override + void down(Schema schema) { + schema.drop('foos', cascade: true); + } +} + +class FooPivotMigration extends Migration { + @override + void up(Schema schema) { + schema.create('foo_pivots', (table) { + table + .declare('weird_join_id', ColumnType('int')) + .references('weird_joins', 'id'); + table.declare('foo_bar', ColumnType('varchar')).references('foos', 'bar'); + }); + } + + @override + void down(Schema schema) { + schema.drop('foo_pivots'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class UnorthodoxQuery extends Query { + UnorthodoxQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UnorthodoxQueryWhere(this); + } + + @override + final UnorthodoxQueryValues values = UnorthodoxQueryValues(); + + List _selectedFields = []; + + UnorthodoxQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'unorthodoxes'; + } + + @override + List get fields { + const _fields = ['name']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UnorthodoxQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UnorthodoxQueryWhere? get where { + return _where; + } + + @override + UnorthodoxQueryWhere newWhereClause() { + return UnorthodoxQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = + Unorthodox(name: fields.contains('name') ? (row[0] as String?) : null); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class UnorthodoxQueryWhere extends QueryWhere { + UnorthodoxQueryWhere(UnorthodoxQuery query) + : name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [name]; + } +} + +class UnorthodoxQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Unorthodox model) { + name = model.name; + } +} + +class WeirdJoinQuery extends Query { + WeirdJoinQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = WeirdJoinQueryWhere(this); + leftJoin( + _unorthodox = UnorthodoxQuery( + trampoline: trampoline, + parent: this, + ), + 'join_name', + 'name', + additionalFields: const ['name'], + trampoline: trampoline, + ); + leftJoin( + _song = SongQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'weird_join_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'weird_join_id', + 'title', + ], + trampoline: trampoline, + ); + leftJoin( + _numbas = NumbaQuery( + trampoline: trampoline, + parent: this, + ), + 'id', + 'parent', + additionalFields: const [ + 'i', + 'parent', + ], + trampoline: trampoline, + ); + leftJoin( + '(SELECT foo_pivots.weird_join_id, foos.bar FROM foos LEFT JOIN foo_pivots ON foo_pivots.foo_bar=foos.bar)', + 'id', + 'weird_join_id', + additionalFields: const ['bar'], + trampoline: trampoline, + ); + } + + @override + final WeirdJoinQueryValues values = WeirdJoinQueryValues(); + + List _selectedFields = []; + + WeirdJoinQueryWhere? _where; + + late UnorthodoxQuery _unorthodox; + + late SongQuery _song; + + late NumbaQuery _numbas; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'weird_joins'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'join_name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + WeirdJoinQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + WeirdJoinQueryWhere? get where { + return _where; + } + + @override + WeirdJoinQueryWhere newWhereClause() { + return WeirdJoinQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = WeirdJoin(id: fields.contains('id') ? (row[0] as int) : 0); + if (row.length > 2) { + var modelOpt = UnorthodoxQuery().parseRow(row.skip(2).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(unorthodox: m); + }); + } + if (row.length > 3) { + var modelOpt = SongQuery().parseRow(row.skip(3).take(5).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(song: m); + }); + } + if (row.length > 8) { + var modelOpt = NumbaQuery().parseRow(row.skip(8).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(numbas: [m]); + }); + } + if (row.length > 10) { + var modelOpt = FooQuery().parseRow(row.skip(10).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foos: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + UnorthodoxQuery get unorthodox { + return _unorthodox; + } + + SongQuery get song { + return _song; + } + + NumbaQuery get numbas { + return _numbas; + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('weird_joins') && + trampoline.contains('foo_pivots'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + numbas: List<_Numba>.from(l.numbas)..addAll(model.numbas), + foos: List<_Foo>.from(l.foos)..addAll(model.foos)); + } + }); + }); + } +} + +class WeirdJoinQueryWhere extends QueryWhere { + WeirdJoinQueryWhere(WeirdJoinQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + joinName = StringSqlExpressionBuilder( + query, + 'join_name', + ); + + final NumericSqlExpressionBuilder id; + + final StringSqlExpressionBuilder joinName; + + @override + List get expressionBuilders { + return [ + id, + joinName, + ]; + } +} + +class WeirdJoinQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int get id { + return (values['id'] as int); + } + + set id(int value) => values['id'] = value; + + String? get joinName { + return (values['join_name'] as String?); + } + + set joinName(String? value) => values['join_name'] = value; + + void copyFrom(WeirdJoin model) { + id = model.id; + if (model.unorthodox != null) { + values['join_name'] = model.unorthodox?.name; + } + } +} + +class SongQuery extends Query { + SongQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = SongQueryWhere(this); + } + + @override + final SongQueryValues values = SongQueryValues(); + + List _selectedFields = []; + + SongQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'songs'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'weird_join_id', + 'title', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + SongQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + SongQueryWhere? get where { + return _where; + } + + @override + SongQueryWhere newWhereClause() { + return SongQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Song( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + weirdJoinId: fields.contains('weird_join_id') ? (row[3] as int?) : null, + title: fields.contains('title') ? (row[4] as String?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class SongQueryWhere extends QueryWhere { + SongQueryWhere(SongQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + weirdJoinId = NumericSqlExpressionBuilder( + query, + 'weird_join_id', + ), + title = StringSqlExpressionBuilder( + query, + 'title', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final NumericSqlExpressionBuilder weirdJoinId; + + final StringSqlExpressionBuilder title; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]; + } +} + +class SongQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + int? get weirdJoinId { + return (values['weird_join_id'] as int?); + } + + set weirdJoinId(int? value) => values['weird_join_id'] = value; + + String? get title { + return (values['title'] as String?); + } + + set title(String? value) => values['title'] = value; + + void copyFrom(Song model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + weirdJoinId = model.weirdJoinId; + title = model.title; + } +} + +class NumbaQuery extends Query { + NumbaQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = NumbaQueryWhere(this); + } + + @override + final NumbaQueryValues values = NumbaQueryValues(); + + List _selectedFields = []; + + NumbaQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'numbas'; + } + + @override + List get fields { + const _fields = [ + 'i', + 'parent', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + NumbaQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + NumbaQueryWhere? get where { + return _where; + } + + @override + NumbaQueryWhere newWhereClause() { + return NumbaQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Numba( + i: fields.contains('i') ? (row[0] as int) : 0, + parent: fields.contains('parent') ? (row[1] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class NumbaQueryWhere extends QueryWhere { + NumbaQueryWhere(NumbaQuery query) + : i = NumericSqlExpressionBuilder( + query, + 'i', + ), + parent = NumericSqlExpressionBuilder( + query, + 'parent', + ); + + final NumericSqlExpressionBuilder i; + + final NumericSqlExpressionBuilder parent; + + @override + List get expressionBuilders { + return [ + i, + parent, + ]; + } +} + +class NumbaQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int get i { + return (values['i'] as int); + } + + set i(int value) => values['i'] = value; + + int? get parent { + return (values['parent'] as int?); + } + + set parent(int? value) => values['parent'] = value; + + void copyFrom(Numba model) { + i = model.i; + parent = model.parent; + } +} + +class FooQuery extends Query { + FooQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FooQueryWhere(this); + leftJoin( + '(SELECT foo_pivots.foo_bar, weird_joins.id, weird_joins.join_name FROM weird_joins LEFT JOIN foo_pivots ON foo_pivots.weird_join_id=weird_joins.id)', + 'bar', + 'foo_bar', + additionalFields: const [ + 'id', + 'join_name', + ], + trampoline: trampoline, + ); + } + + @override + final FooQueryValues values = FooQueryValues(); + + List _selectedFields = []; + + FooQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'foos'; + } + + @override + List get fields { + const _fields = ['bar']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FooQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FooQueryWhere? get where { + return _where; + } + + @override + FooQueryWhere newWhereClause() { + return FooQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Foo(bar: fields.contains('bar') ? (row[0] as String?) : null); + if (row.length > 1) { + var modelOpt = WeirdJoinQuery().parseRow(row.skip(1).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(weirdJoins: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('foos') && + trampoline.contains('foo_pivots'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.bar == model.bar); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + weirdJoins: List<_WeirdJoin>.from(l.weirdJoins) + ..addAll(model.weirdJoins)); + } + }); + }); + } +} + +class FooQueryWhere extends QueryWhere { + FooQueryWhere(FooQuery query) + : bar = StringSqlExpressionBuilder( + query, + 'bar', + ); + + final StringSqlExpressionBuilder bar; + + @override + List get expressionBuilders { + return [bar]; + } +} + +class FooQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get bar { + return (values['bar'] as String?); + } + + set bar(String? value) => values['bar'] = value; + + void copyFrom(Foo model) { + bar = model.bar; + } +} + +class FooPivotQuery extends Query { + FooPivotQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = FooPivotQueryWhere(this); + leftJoin( + _weirdJoin = WeirdJoinQuery( + trampoline: trampoline, + parent: this, + ), + 'weird_join_id', + 'id', + additionalFields: const [ + 'id', + 'join_name', + ], + trampoline: trampoline, + ); + leftJoin( + _foo = FooQuery( + trampoline: trampoline, + parent: this, + ), + 'foo_bar', + 'bar', + additionalFields: const ['bar'], + trampoline: trampoline, + ); + } + + @override + final FooPivotQueryValues values = FooPivotQueryValues(); + + List _selectedFields = []; + + FooPivotQueryWhere? _where; + + late WeirdJoinQuery _weirdJoin; + + late FooQuery _foo; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'foo_pivots'; + } + + @override + List get fields { + const _fields = [ + 'weird_join_id', + 'foo_bar', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + FooPivotQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + FooPivotQueryWhere? get where { + return _where; + } + + @override + FooPivotQueryWhere newWhereClause() { + return FooPivotQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = FooPivot(); + if (row.length > 2) { + var modelOpt = WeirdJoinQuery().parseRow(row.skip(2).take(2).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(weirdJoin: m); + }); + } + if (row.length > 4) { + var modelOpt = FooQuery().parseRow(row.skip(4).take(1).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(foo: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + WeirdJoinQuery get weirdJoin { + return _weirdJoin; + } + + FooQuery get foo { + return _foo; + } +} + +class FooPivotQueryWhere extends QueryWhere { + FooPivotQueryWhere(FooPivotQuery query) + : weirdJoinId = NumericSqlExpressionBuilder( + query, + 'weird_join_id', + ), + fooBar = StringSqlExpressionBuilder( + query, + 'foo_bar', + ); + + final NumericSqlExpressionBuilder weirdJoinId; + + final StringSqlExpressionBuilder fooBar; + + @override + List get expressionBuilders { + return [ + weirdJoinId, + fooBar, + ]; + } +} + +class FooPivotQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int get weirdJoinId { + return (values['weird_join_id'] as int); + } + + set weirdJoinId(int value) => values['weird_join_id'] = value; + + String? get fooBar { + return (values['foo_bar'] as String?); + } + + set fooBar(String? value) => values['foo_bar'] = value; + + void copyFrom(FooPivot model) { + if (model.weirdJoin != null) { + values['weird_join_id'] = model.weirdJoin?.id; + } + if (model.foo != null) { + values['foo_bar'] = model.foo?.bar; + } + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Unorthodox implements _Unorthodox { + Unorthodox({this.name}); + + @override + String? name; + + Unorthodox copyWith({String? name}) { + return Unorthodox(name: name ?? this.name); + } + + @override + bool operator ==(other) { + return other is _Unorthodox && other.name == name; + } + + @override + int get hashCode { + return hashObjects([name]); + } + + @override + String toString() { + return 'Unorthodox(name=$name)'; + } + + Map toJson() { + return UnorthodoxSerializer.toMap(this); + } +} + +@generatedSerializable +class WeirdJoin implements _WeirdJoin { + WeirdJoin({ + required this.id, + this.unorthodox, + this.song, + this.numbas = const [], + this.foos = const [], + }); + + @override + int id; + + @override + _Unorthodox? unorthodox; + + @override + _Song? song; + + @override + List<_Numba> numbas; + + @override + List<_Foo> foos; + + WeirdJoin copyWith({ + int? id, + _Unorthodox? unorthodox, + _Song? song, + List<_Numba>? numbas, + List<_Foo>? foos, + }) { + return WeirdJoin( + id: id ?? this.id, + unorthodox: unorthodox ?? this.unorthodox, + song: song ?? this.song, + numbas: numbas ?? this.numbas, + foos: foos ?? this.foos); + } + + @override + bool operator ==(other) { + return other is _WeirdJoin && + other.id == id && + other.unorthodox == unorthodox && + other.song == song && + ListEquality<_Numba>(DefaultEquality<_Numba>()) + .equals(other.numbas, numbas) && + ListEquality<_Foo>(DefaultEquality<_Foo>()).equals(other.foos, foos); + } + + @override + int get hashCode { + return hashObjects([ + id, + unorthodox, + song, + numbas, + foos, + ]); + } + + @override + String toString() { + return 'WeirdJoin(id=$id, unorthodox=$unorthodox, song=$song, numbas=$numbas, foos=$foos)'; + } + + Map toJson() { + return WeirdJoinSerializer.toMap(this); + } +} + +@generatedSerializable +class Song extends _Song { + Song({ + this.id, + this.createdAt, + this.updatedAt, + this.weirdJoinId, + this.title, + }); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + int? weirdJoinId; + + @override + String? title; + + Song copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + int? weirdJoinId, + String? title, + }) { + return Song( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + weirdJoinId: weirdJoinId ?? this.weirdJoinId, + title: title ?? this.title); + } + + @override + bool operator ==(other) { + return other is _Song && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.weirdJoinId == weirdJoinId && + other.title == title; + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]); + } + + @override + String toString() { + return 'Song(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, weirdJoinId=$weirdJoinId, title=$title)'; + } + + Map toJson() { + return SongSerializer.toMap(this); + } +} + +@generatedSerializable +class Numba extends _Numba { + Numba({ + required this.i, + this.parent, + }); + + @override + int i; + + @override + int? parent; + + Numba copyWith({ + int? i, + int? parent, + }) { + return Numba(i: i ?? this.i, parent: parent ?? this.parent); + } + + @override + bool operator ==(other) { + return other is _Numba && other.i == i && other.parent == parent; + } + + @override + int get hashCode { + return hashObjects([ + i, + parent, + ]); + } + + @override + String toString() { + return 'Numba(i=$i, parent=$parent)'; + } + + Map toJson() { + return NumbaSerializer.toMap(this); + } +} + +@generatedSerializable +class Foo implements _Foo { + Foo({ + this.bar, + this.weirdJoins = const [], + }); + + @override + String? bar; + + @override + List<_WeirdJoin> weirdJoins; + + Foo copyWith({ + String? bar, + List<_WeirdJoin>? weirdJoins, + }) { + return Foo(bar: bar ?? this.bar, weirdJoins: weirdJoins ?? this.weirdJoins); + } + + @override + bool operator ==(other) { + return other is _Foo && + other.bar == bar && + ListEquality<_WeirdJoin>(DefaultEquality<_WeirdJoin>()) + .equals(other.weirdJoins, weirdJoins); + } + + @override + int get hashCode { + return hashObjects([ + bar, + weirdJoins, + ]); + } + + @override + String toString() { + return 'Foo(bar=$bar, weirdJoins=$weirdJoins)'; + } + + Map toJson() { + return FooSerializer.toMap(this); + } +} + +@generatedSerializable +class FooPivot implements _FooPivot { + FooPivot({ + this.weirdJoin, + this.foo, + }); + + @override + _WeirdJoin? weirdJoin; + + @override + _Foo? foo; + + FooPivot copyWith({ + _WeirdJoin? weirdJoin, + _Foo? foo, + }) { + return FooPivot( + weirdJoin: weirdJoin ?? this.weirdJoin, foo: foo ?? this.foo); + } + + @override + bool operator ==(other) { + return other is _FooPivot && + other.weirdJoin == weirdJoin && + other.foo == foo; + } + + @override + int get hashCode { + return hashObjects([ + weirdJoin, + foo, + ]); + } + + @override + String toString() { + return 'FooPivot(weirdJoin=$weirdJoin, foo=$foo)'; + } + + Map toJson() { + return FooPivotSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const UnorthodoxSerializer unorthodoxSerializer = UnorthodoxSerializer(); + +class UnorthodoxEncoder extends Converter { + const UnorthodoxEncoder(); + + @override + Map convert(Unorthodox model) => UnorthodoxSerializer.toMap(model); +} + +class UnorthodoxDecoder extends Converter { + const UnorthodoxDecoder(); + + @override + Unorthodox convert(Map map) => UnorthodoxSerializer.fromMap(map); +} + +class UnorthodoxSerializer extends Codec { + const UnorthodoxSerializer(); + + @override + UnorthodoxEncoder get encoder => const UnorthodoxEncoder(); + + @override + UnorthodoxDecoder get decoder => const UnorthodoxDecoder(); + + static Unorthodox fromMap(Map map) { + return Unorthodox(name: map['name'] as String?); + } + + static Map toMap(_Unorthodox? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'name': model.name}; + } +} + +abstract class UnorthodoxFields { + static const List allFields = [name]; + + static const String name = 'name'; +} + +const WeirdJoinSerializer weirdJoinSerializer = WeirdJoinSerializer(); + +class WeirdJoinEncoder extends Converter { + const WeirdJoinEncoder(); + + @override + Map convert(WeirdJoin model) => WeirdJoinSerializer.toMap(model); +} + +class WeirdJoinDecoder extends Converter { + const WeirdJoinDecoder(); + + @override + WeirdJoin convert(Map map) => WeirdJoinSerializer.fromMap(map); +} + +class WeirdJoinSerializer extends Codec { + const WeirdJoinSerializer(); + + @override + WeirdJoinEncoder get encoder => const WeirdJoinEncoder(); + + @override + WeirdJoinDecoder get decoder => const WeirdJoinDecoder(); + + static WeirdJoin fromMap(Map map) { + return WeirdJoin( + id: map['id'] as int, + unorthodox: map['unorthodox'] != null + ? UnorthodoxSerializer.fromMap(map['unorthodox'] as Map) + : null, + song: map['song'] != null + ? SongSerializer.fromMap(map['song'] as Map) + : null, + numbas: map['numbas'] is Iterable + ? List.unmodifiable(((map['numbas'] as Iterable).whereType()) + .map(NumbaSerializer.fromMap)) + : [], + foos: map['foos'] is Iterable + ? List.unmodifiable(((map['foos'] as Iterable).whereType()) + .map(FooSerializer.fromMap)) + : []); + } + + static Map toMap(_WeirdJoin? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'unorthodox': UnorthodoxSerializer.toMap(model.unorthodox), + 'song': SongSerializer.toMap(model.song), + 'numbas': model.numbas.map((m) => NumbaSerializer.toMap(m)).toList(), + 'foos': model.foos.map((m) => FooSerializer.toMap(m)).toList() + }; + } +} + +abstract class WeirdJoinFields { + static const List allFields = [ + id, + unorthodox, + song, + numbas, + foos, + ]; + + static const String id = 'id'; + + static const String unorthodox = 'unorthodox'; + + static const String song = 'song'; + + static const String numbas = 'numbas'; + + static const String foos = 'foos'; +} + +const SongSerializer songSerializer = SongSerializer(); + +class SongEncoder extends Converter { + const SongEncoder(); + + @override + Map convert(Song model) => SongSerializer.toMap(model); +} + +class SongDecoder extends Converter { + const SongDecoder(); + + @override + Song convert(Map map) => SongSerializer.fromMap(map); +} + +class SongSerializer extends Codec { + const SongSerializer(); + + @override + SongEncoder get encoder => const SongEncoder(); + + @override + SongDecoder get decoder => const SongDecoder(); + + static Song fromMap(Map map) { + return Song( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + weirdJoinId: map['weird_join_id'] as int?, + title: map['title'] as String?); + } + + static Map toMap(_Song? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'weird_join_id': model.weirdJoinId, + 'title': model.title + }; + } +} + +abstract class SongFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + weirdJoinId, + title, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String weirdJoinId = 'weird_join_id'; + + static const String title = 'title'; +} + +const NumbaSerializer numbaSerializer = NumbaSerializer(); + +class NumbaEncoder extends Converter { + const NumbaEncoder(); + + @override + Map convert(Numba model) => NumbaSerializer.toMap(model); +} + +class NumbaDecoder extends Converter { + const NumbaDecoder(); + + @override + Numba convert(Map map) => NumbaSerializer.fromMap(map); +} + +class NumbaSerializer extends Codec { + const NumbaSerializer(); + + @override + NumbaEncoder get encoder => const NumbaEncoder(); + + @override + NumbaDecoder get decoder => const NumbaDecoder(); + + static Numba fromMap(Map map) { + return Numba(i: map['i'] as int, parent: map['parent'] as int?); + } + + static Map toMap(_Numba? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'i': model.i, 'parent': model.parent}; + } +} + +abstract class NumbaFields { + static const List allFields = [ + i, + parent, + ]; + + static const String i = 'i'; + + static const String parent = 'parent'; +} + +const FooSerializer fooSerializer = FooSerializer(); + +class FooEncoder extends Converter { + const FooEncoder(); + + @override + Map convert(Foo model) => FooSerializer.toMap(model); +} + +class FooDecoder extends Converter { + const FooDecoder(); + + @override + Foo convert(Map map) => FooSerializer.fromMap(map); +} + +class FooSerializer extends Codec { + const FooSerializer(); + + @override + FooEncoder get encoder => const FooEncoder(); + + @override + FooDecoder get decoder => const FooDecoder(); + + static Foo fromMap(Map map) { + return Foo( + bar: map['bar'] as String?, + weirdJoins: map['weird_joins'] is Iterable + ? List.unmodifiable( + ((map['weird_joins'] as Iterable).whereType()) + .map(WeirdJoinSerializer.fromMap)) + : []); + } + + static Map toMap(_Foo? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'bar': model.bar, + 'weird_joins': + model.weirdJoins.map((m) => WeirdJoinSerializer.toMap(m)).toList() + }; + } +} + +abstract class FooFields { + static const List allFields = [ + bar, + weirdJoins, + ]; + + static const String bar = 'bar'; + + static const String weirdJoins = 'weird_joins'; +} + +const FooPivotSerializer fooPivotSerializer = FooPivotSerializer(); + +class FooPivotEncoder extends Converter { + const FooPivotEncoder(); + + @override + Map convert(FooPivot model) => FooPivotSerializer.toMap(model); +} + +class FooPivotDecoder extends Converter { + const FooPivotDecoder(); + + @override + FooPivot convert(Map map) => FooPivotSerializer.fromMap(map); +} + +class FooPivotSerializer extends Codec { + const FooPivotSerializer(); + + @override + FooPivotEncoder get encoder => const FooPivotEncoder(); + + @override + FooPivotDecoder get decoder => const FooPivotDecoder(); + + static FooPivot fromMap(Map map) { + return FooPivot( + weirdJoin: map['weird_join'] != null + ? WeirdJoinSerializer.fromMap(map['weird_join'] as Map) + : null, + foo: map['foo'] != null + ? FooSerializer.fromMap(map['foo'] as Map) + : null); + } + + static Map toMap(_FooPivot? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'weird_join': WeirdJoinSerializer.toMap(model.weirdJoin), + 'foo': FooSerializer.toMap(model.foo) + }; + } +} + +abstract class FooPivotFields { + static const List allFields = [ + weirdJoin, + foo, + ]; + + static const String weirdJoin = 'weird_join'; + + static const String foo = 'foo'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/user.dart b/packages/orm/angel_orm_postgres/test/models/user.dart new file mode 100644 index 000000000..27195c60c --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/user.dart @@ -0,0 +1,38 @@ +library; + +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:optional/optional.dart'; + +part 'user.g.dart'; + +@serializable +@orm +abstract class _User extends Model { + String? get username; + String? get password; + String? get email; + + @ManyToMany(_RoleUser) + List<_Role> get roles; +} + +@serializable +@orm +abstract class _RoleUser { + @belongsTo + _Role? get role; + + @belongsTo + _User? get user; +} + +@serializable +@orm +abstract class _Role extends Model { + String? name; + + @ManyToMany(_RoleUser) + List<_User> get users; +} diff --git a/packages/orm/angel_orm_postgres/test/models/user.g.dart b/packages/orm/angel_orm_postgres/test/models/user.g.dart new file mode 100644 index 000000000..4ed37106e --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/user.g.dart @@ -0,0 +1,1171 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class UserMigration extends Migration { + @override + void up(Schema schema) { + schema.create('users', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('username', length: 255); + table.varChar('password', length: 255); + table.varChar('email', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('users', cascade: true); + } +} + +class RoleUserMigration extends Migration { + @override + void up(Schema schema) { + schema.create('role_users', (table) { + table.declare('role_id', ColumnType('int')).references('roles', 'id'); + table.declare('user_id', ColumnType('int')).references('users', 'id'); + }); + } + + @override + void down(Schema schema) { + schema.drop('role_users'); + } +} + +class RoleMigration extends Migration { + @override + void up(Schema schema) { + schema.create('roles', (table) { + table.serial('id').primaryKey(); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + table.varChar('name', length: 255); + }); + } + + @override + void down(Schema schema) { + schema.drop('roles', cascade: true); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class UserQuery extends Query { + UserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = UserQueryWhere(this); + leftJoin( + '(SELECT role_users.user_id, roles.id, roles.created_at, roles.updated_at, roles.name FROM roles LEFT JOIN role_users ON role_users.role_id=roles.id)', + 'id', + 'user_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + } + + @override + final UserQueryValues values = UserQueryValues(); + + List _selectedFields = []; + + UserQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'users'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + UserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + UserQueryWhere? get where { + return _where; + } + + @override + UserQueryWhere newWhereClause() { + return UserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = User( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + username: fields.contains('username') ? (row[3] as String?) : null, + password: fields.contains('password') ? (row[4] as String?) : null, + email: fields.contains('email') ? (row[5] as String?) : null, + ); + if (row.length > 6) { + var modelOpt = RoleQuery().parseRow(row.skip(6).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(roles: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('users') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + roles: List<_Role>.from(l.roles)..addAll(model.roles)); + } + }); + }); + } +} + +class UserQueryWhere extends QueryWhere { + UserQueryWhere(UserQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + username = StringSqlExpressionBuilder( + query, + 'username', + ), + password = StringSqlExpressionBuilder( + query, + 'password', + ), + email = StringSqlExpressionBuilder( + query, + 'email', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder username; + + final StringSqlExpressionBuilder password; + + final StringSqlExpressionBuilder email; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + username, + password, + email, + ]; + } +} + +class UserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get username { + return (values['username'] as String?); + } + + set username(String? value) => values['username'] = value; + + String? get password { + return (values['password'] as String?); + } + + set password(String? value) => values['password'] = value; + + String? get email { + return (values['email'] as String?); + } + + set email(String? value) => values['email'] = value; + + void copyFrom(User model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + username = model.username; + password = model.password; + email = model.email; + } +} + +class RoleUserQuery extends Query { + RoleUserQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleUserQueryWhere(this); + leftJoin( + _role = RoleQuery( + trampoline: trampoline, + parent: this, + ), + 'role_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'name', + ], + trampoline: trampoline, + ); + leftJoin( + _user = UserQuery( + trampoline: trampoline, + parent: this, + ), + 'user_id', + 'id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ], + trampoline: trampoline, + ); + } + + @override + final RoleUserQueryValues values = RoleUserQueryValues(); + + List _selectedFields = []; + + RoleUserQueryWhere? _where; + + late RoleQuery _role; + + late UserQuery _user; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'role_users'; + } + + @override + List get fields { + const _fields = [ + 'role_id', + 'user_id', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleUserQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleUserQueryWhere? get where { + return _where; + } + + @override + RoleUserQueryWhere newWhereClause() { + return RoleUserQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = RoleUser(); + if (row.length > 2) { + var modelOpt = RoleQuery().parseRow(row.skip(2).take(4).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(role: m); + }); + } + if (row.length > 6) { + var modelOpt = UserQuery().parseRow(row.skip(6).take(6).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(user: m); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + RoleQuery get role { + return _role; + } + + UserQuery get user { + return _user; + } +} + +class RoleUserQueryWhere extends QueryWhere { + RoleUserQueryWhere(RoleUserQuery query) + : roleId = NumericSqlExpressionBuilder( + query, + 'role_id', + ), + userId = NumericSqlExpressionBuilder( + query, + 'user_id', + ); + + final NumericSqlExpressionBuilder roleId; + + final NumericSqlExpressionBuilder userId; + + @override + List get expressionBuilders { + return [ + roleId, + userId, + ]; + } +} + +class RoleUserQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int get roleId { + return (values['role_id'] as int); + } + + set roleId(int value) => values['role_id'] = value; + + int get userId { + return (values['user_id'] as int); + } + + set userId(int value) => values['user_id'] = value; + + void copyFrom(RoleUser model) { + if (model.role != null) { + values['role_id'] = model.role?.id; + } + if (model.user != null) { + values['user_id'] = model.user?.id; + } + } +} + +class RoleQuery extends Query { + RoleQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = RoleQueryWhere(this); + leftJoin( + '(SELECT role_users.role_id, users.id, users.created_at, users.updated_at, users.username, users.password, users.email FROM users LEFT JOIN role_users ON role_users.user_id=users.id)', + 'id', + 'role_id', + additionalFields: const [ + 'id', + 'created_at', + 'updated_at', + 'username', + 'password', + 'email', + ], + trampoline: trampoline, + ); + } + + @override + final RoleQueryValues values = RoleQueryValues(); + + List _selectedFields = []; + + RoleQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'roles'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'created_at', + 'updated_at', + 'name', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + RoleQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + RoleQueryWhere? get where { + return _where; + } + + @override + RoleQueryWhere newWhereClause() { + return RoleQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = Role( + id: fields.contains('id') ? row[0].toString() : null, + createdAt: + fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, + updatedAt: + fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, + name: fields.contains('name') ? (row[3] as String?) : null, + ); + if (row.length > 4) { + var modelOpt = UserQuery().parseRow(row.skip(4).take(6).toList()); + modelOpt.ifPresent((m) { + model = model.copyWith(users: [m]); + }); + } + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } + + @override + bool canCompile(trampoline) { + return (!(trampoline.contains('roles') && + trampoline.contains('role_users'))); + } + + @override + Future> get(QueryExecutor executor) { + return super.get(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> update(QueryExecutor executor) { + return super.update(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } + + @override + Future> delete(QueryExecutor executor) { + return super.delete(executor).then((result) { + return result.fold>([], (out, model) { + var idx = out.indexWhere((m) => m.id == model.id); + + if (idx == -1) { + return out..add(model); + } else { + var l = out[idx]; + return out + ..[idx] = l.copyWith( + users: List<_User>.from(l.users)..addAll(model.users)); + } + }); + }); + } +} + +class RoleQueryWhere extends QueryWhere { + RoleQueryWhere(RoleQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + createdAt = DateTimeSqlExpressionBuilder( + query, + 'created_at', + ), + updatedAt = DateTimeSqlExpressionBuilder( + query, + 'updated_at', + ), + name = StringSqlExpressionBuilder( + query, + 'name', + ); + + final NumericSqlExpressionBuilder id; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + final StringSqlExpressionBuilder name; + + @override + List get expressionBuilders { + return [ + id, + createdAt, + updatedAt, + name, + ]; + } +} + +class RoleQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + String? get id { + return (values['id'] as String?); + } + + set id(String? value) => values['id'] = value; + + DateTime? get createdAt { + return (values['created_at'] as DateTime?); + } + + set createdAt(DateTime? value) => values['created_at'] = value; + + DateTime? get updatedAt { + return (values['updated_at'] as DateTime?); + } + + set updatedAt(DateTime? value) => values['updated_at'] = value; + + String? get name { + return (values['name'] as String?); + } + + set name(String? value) => values['name'] = value; + + void copyFrom(Role model) { + createdAt = model.createdAt; + updatedAt = model.updatedAt; + name = model.name; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class User extends _User { + User({ + this.id, + this.createdAt, + this.updatedAt, + this.username, + this.password, + this.email, + List<_Role> roles = const [], + }) : roles = List.unmodifiable(roles); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? username; + + @override + String? password; + + @override + String? email; + + @override + List<_Role> roles; + + User copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? username, + String? password, + String? email, + List<_Role>? roles, + }) { + return User( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + username: username ?? this.username, + password: password ?? this.password, + email: email ?? this.email, + roles: roles ?? this.roles); + } + + @override + bool operator ==(other) { + return other is _User && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.username == username && + other.password == password && + other.email == email && + ListEquality<_Role>(DefaultEquality<_Role>()) + .equals(other.roles, roles); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + username, + password, + email, + roles, + ]); + } + + @override + String toString() { + return 'User(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, username=$username, password=$password, email=$email, roles=$roles)'; + } + + Map toJson() { + return UserSerializer.toMap(this); + } +} + +@generatedSerializable +class RoleUser implements _RoleUser { + RoleUser({ + this.role, + this.user, + }); + + @override + _Role? role; + + @override + _User? user; + + RoleUser copyWith({ + _Role? role, + _User? user, + }) { + return RoleUser(role: role ?? this.role, user: user ?? this.user); + } + + @override + bool operator ==(other) { + return other is _RoleUser && other.role == role && other.user == user; + } + + @override + int get hashCode { + return hashObjects([ + role, + user, + ]); + } + + @override + String toString() { + return 'RoleUser(role=$role, user=$user)'; + } + + Map toJson() { + return RoleUserSerializer.toMap(this); + } +} + +@generatedSerializable +class Role extends _Role { + Role({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + List<_User> users = const [], + }) : users = List.unmodifiable(users); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; + + @override + String? name; + + @override + List<_User> users; + + Role copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? name, + List<_User>? users, + }) { + return Role( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + name: name ?? this.name, + users: users ?? this.users); + } + + @override + bool operator ==(other) { + return other is _Role && + other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.name == name && + ListEquality<_User>(DefaultEquality<_User>()) + .equals(other.users, users); + } + + @override + int get hashCode { + return hashObjects([ + id, + createdAt, + updatedAt, + name, + users, + ]); + } + + @override + String toString() { + return 'Role(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, name=$name, users=$users)'; + } + + Map toJson() { + return RoleSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const UserSerializer userSerializer = UserSerializer(); + +class UserEncoder extends Converter { + const UserEncoder(); + + @override + Map convert(User model) => UserSerializer.toMap(model); +} + +class UserDecoder extends Converter { + const UserDecoder(); + + @override + User convert(Map map) => UserSerializer.fromMap(map); +} + +class UserSerializer extends Codec { + const UserSerializer(); + + @override + UserEncoder get encoder => const UserEncoder(); + + @override + UserDecoder get decoder => const UserDecoder(); + + static User fromMap(Map map) { + return User( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + username: map['username'] as String?, + password: map['password'] as String?, + email: map['email'] as String?, + roles: map['roles'] is Iterable + ? List.unmodifiable(((map['roles'] as Iterable).whereType()) + .map(RoleSerializer.fromMap)) + : []); + } + + static Map toMap(_User? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'username': model.username, + 'password': model.password, + 'email': model.email, + 'roles': model.roles.map((m) => RoleSerializer.toMap(m)).toList() + }; + } +} + +abstract class UserFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + username, + password, + email, + roles, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String username = 'username'; + + static const String password = 'password'; + + static const String email = 'email'; + + static const String roles = 'roles'; +} + +const RoleUserSerializer roleUserSerializer = RoleUserSerializer(); + +class RoleUserEncoder extends Converter { + const RoleUserEncoder(); + + @override + Map convert(RoleUser model) => RoleUserSerializer.toMap(model); +} + +class RoleUserDecoder extends Converter { + const RoleUserDecoder(); + + @override + RoleUser convert(Map map) => RoleUserSerializer.fromMap(map); +} + +class RoleUserSerializer extends Codec { + const RoleUserSerializer(); + + @override + RoleUserEncoder get encoder => const RoleUserEncoder(); + + @override + RoleUserDecoder get decoder => const RoleUserDecoder(); + + static RoleUser fromMap(Map map) { + return RoleUser( + role: map['role'] != null + ? RoleSerializer.fromMap(map['role'] as Map) + : null, + user: map['user'] != null + ? UserSerializer.fromMap(map['user'] as Map) + : null); + } + + static Map toMap(_RoleUser? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'role': RoleSerializer.toMap(model.role), + 'user': UserSerializer.toMap(model.user) + }; + } +} + +abstract class RoleUserFields { + static const List allFields = [ + role, + user, + ]; + + static const String role = 'role'; + + static const String user = 'user'; +} + +const RoleSerializer roleSerializer = RoleSerializer(); + +class RoleEncoder extends Converter { + const RoleEncoder(); + + @override + Map convert(Role model) => RoleSerializer.toMap(model); +} + +class RoleDecoder extends Converter { + const RoleDecoder(); + + @override + Role convert(Map map) => RoleSerializer.fromMap(map); +} + +class RoleSerializer extends Codec { + const RoleSerializer(); + + @override + RoleEncoder get encoder => const RoleEncoder(); + + @override + RoleDecoder get decoder => const RoleDecoder(); + + static Role fromMap(Map map) { + return Role( + id: map['id'] as String?, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null, + name: map['name'] as String?, + users: map['users'] is Iterable + ? List.unmodifiable(((map['users'] as Iterable).whereType()) + .map(UserSerializer.fromMap)) + : []); + } + + static Map toMap(_Role? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return { + 'id': model.id, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String(), + 'name': model.name, + 'users': model.users.map((m) => UserSerializer.toMap(m)).toList() + }; + } +} + +abstract class RoleFields { + static const List allFields = [ + id, + createdAt, + updatedAt, + name, + users, + ]; + + static const String id = 'id'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; + + static const String name = 'name'; + + static const String users = 'users'; +} diff --git a/packages/orm/angel_orm_postgres/test/models/world.dart b/packages/orm/angel_orm_postgres/test/models/world.dart new file mode 100644 index 000000000..313762685 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/world.dart @@ -0,0 +1,16 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:optional/optional.dart'; + +part 'world.g.dart'; + +@serializable +@Orm(tableName: 'world') +abstract class _World { + @primaryKey + int? id; + + @Column() + int? randomNumber; +} diff --git a/packages/orm/angel_orm_postgres/test/models/world.g.dart b/packages/orm/angel_orm_postgres/test/models/world.g.dart new file mode 100644 index 000000000..14a63b81b --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/models/world.g.dart @@ -0,0 +1,247 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'world.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class WorldMigration extends Migration { + @override + void up(Schema schema) { + schema.create('world', (table) { + table.integer('id').primaryKey(); + table.integer('random_number'); + }); + } + + @override + void down(Schema schema) { + schema.drop('world'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class WorldQuery extends Query { + WorldQuery({ + Query? parent, + Set? trampoline, + }) : super(parent: parent) { + trampoline ??= {}; + trampoline.add(tableName); + _where = WorldQueryWhere(this); + } + + @override + final WorldQueryValues values = WorldQueryValues(); + + List _selectedFields = []; + + WorldQueryWhere? _where; + + @override + Map get casts { + return {}; + } + + @override + String get tableName { + return 'world'; + } + + @override + List get fields { + const _fields = [ + 'id', + 'random_number', + ]; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + WorldQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; + } + + @override + WorldQueryWhere? get where { + return _where; + } + + @override + WorldQueryWhere newWhereClause() { + return WorldQueryWhere(this); + } + + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } + var model = World( + id: fields.contains('id') ? (row[0] as int?) : null, + randomNumber: fields.contains('random_number') ? (row[1] as int?) : null, + ); + return Optional.of(model); + } + + @override + Optional deserialize(List row) { + return parseRow(row); + } +} + +class WorldQueryWhere extends QueryWhere { + WorldQueryWhere(WorldQuery query) + : id = NumericSqlExpressionBuilder( + query, + 'id', + ), + randomNumber = NumericSqlExpressionBuilder( + query, + 'random_number', + ); + + final NumericSqlExpressionBuilder id; + + final NumericSqlExpressionBuilder randomNumber; + + @override + List get expressionBuilders { + return [ + id, + randomNumber, + ]; + } +} + +class WorldQueryValues extends MapQueryValues { + @override + Map get casts { + return {}; + } + + int? get id { + return (values['id'] as int?); + } + + set id(int? value) => values['id'] = value; + + int? get randomNumber { + return (values['random_number'] as int?); + } + + set randomNumber(int? value) => values['random_number'] = value; + + void copyFrom(World model) { + id = model.id; + randomNumber = model.randomNumber; + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class World extends _World { + World({ + this.id, + this.randomNumber, + }); + + @override + int? id; + + @override + int? randomNumber; + + World copyWith({ + int? id, + int? randomNumber, + }) { + return World( + id: id ?? this.id, randomNumber: randomNumber ?? this.randomNumber); + } + + @override + bool operator ==(other) { + return other is _World && + other.id == id && + other.randomNumber == randomNumber; + } + + @override + int get hashCode { + return hashObjects([ + id, + randomNumber, + ]); + } + + @override + String toString() { + return 'World(id=$id, randomNumber=$randomNumber)'; + } + + Map toJson() { + return WorldSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +const WorldSerializer worldSerializer = WorldSerializer(); + +class WorldEncoder extends Converter { + const WorldEncoder(); + + @override + Map convert(World model) => WorldSerializer.toMap(model); +} + +class WorldDecoder extends Converter { + const WorldDecoder(); + + @override + World convert(Map map) => WorldSerializer.fromMap(map); +} + +class WorldSerializer extends Codec { + const WorldSerializer(); + + @override + WorldEncoder get encoder => const WorldEncoder(); + + @override + WorldDecoder get decoder => const WorldDecoder(); + + static World fromMap(Map map) { + return World( + id: map['id'] as int?, randomNumber: map['random_number'] as int?); + } + + static Map toMap(_World? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); + } + return {'id': model.id, 'random_number': model.randomNumber}; + } +} + +abstract class WorldFields { + static const List allFields = [ + id, + randomNumber, + ]; + + static const String id = 'id'; + + static const String randomNumber = 'random_number'; +} diff --git a/packages/orm/angel_orm_postgres/test/orm_debug.dart b/packages/orm/angel_orm_postgres/test/orm_debug.dart deleted file mode 100644 index eac9395f1..000000000 --- a/packages/orm/angel_orm_postgres/test/orm_debug.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:angel3_orm_test/angel3_orm_test.dart'; -import 'package:logging/logging.dart'; - -import 'common.dart'; - -void main() async { - hierarchicalLoggingEnabled = true; - Logger.root - ..level = Level.ALL - ..onRecord.listen(print); - //Logger.root.onRecord.listen((rec) { - // print(rec); - // if (rec.error != null) print(rec.error); - // if (rec.stackTrace != null) print(rec.stackTrace); - //}); - - belongsToTests(pg(['author', 'book']), close: closePg); -} diff --git a/packages/orm/angel_orm_postgres/test/performance_test.dart b/packages/orm/angel_orm_postgres/test/performance_test.dart new file mode 100644 index 000000000..c36770485 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/performance_test.dart @@ -0,0 +1,94 @@ +// ignore: library_annotations +@Skip('Only used for debugging issues') + +import 'dart:async'; +import 'dart:math'; + +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:test/test.dart'; + +import 'models/fortune.dart'; +import 'models/world.dart'; + +void performanceTests(FutureOr Function() createExecutor, + {FutureOr Function(QueryExecutor)? close}) { + QueryExecutor? executor; + + //int _sampleSize = 1000; + + int concurrency = 200; + + // Generate a random number between 1 and 10000 + int genRandomId() { + var rand = Random(); + return rand.nextInt(10000) + 1; + } + + setUp(() async { + print("Run setup"); + executor = await createExecutor(); + + /* + for (var i = 0; i < _sampleSize; i++) { + var query = WorldQuery(); + query.values.randomNumber = _genRandomId(); + var world = (await query.insert(executor!)).value; + } + + for (var i = 0; i < _sampleSize; i++) { + var query = FortuneQuery(); + query.values.message = "message ${_genRandomId()}"; + var fortune = (await query.insert(executor!)).value; + } + */ + }); + + tearDown(() => close!(executor!)); + + test('select all concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + // Fire and forget + for (var i = 0; i < concurrency; i++) { + FortuneQuery().get(executor!); + } + + print("Loop time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + + var result = await FortuneQuery().get(executor!); + + print("Final Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); + + test('select one concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + var id = genRandomId(); + var query = WorldQuery()..where?.id.equals(id); + var result = await query.get(executor!); + + print("Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); + + test('update concurrency', () async { + var stopwatch = Stopwatch(); + stopwatch.start(); + + var id = genRandomId(); + var query = WorldQuery()..where?.id.equals(id); + var result = await query.get(executor!); + + print("Time elapsed: ${stopwatch.elapsed.inMilliseconds}"); + stopwatch.stop(); + + expect(result, isNotNull); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/standalone_test.dart b/packages/orm/angel_orm_postgres/test/standalone_test.dart new file mode 100644 index 000000000..47644dfc3 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/standalone_test.dart @@ -0,0 +1,228 @@ +import 'package:angel3_migration_runner/angel3_migration_runner.dart'; +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'models/car.dart'; + +final DateTime y2k = DateTime(2000, 1, 1); + +void main() { + late Connection conn; + late QueryExecutor executor; + late MigrationRunner runner; + + setUp(() async { + conn = await openPgConnection(); + executor = await createExecutor(conn); + runner = await createTables(conn, [CarMigration()]); + }); + + tearDown(() async { + await dropTables(runner); + }); + + test('to where', () { + var query = CarQuery(); + query.where + ?..familyFriendly.isTrue + ..recalledAt.lessThanOrEqualTo(y2k, includeTime: false); + var whereClause = query.where?.compile(tableName: 'cars'); + //print('Where clause: $whereClause'); + expect(whereClause, + 'cars.family_friendly = TRUE AND cars.recalled_at <= \'2000-01-01\''); + }); + + test('parseRow', () { + // 'id', 'created_at', 'updated_at', 'make', 'description', 'family_friendly', 'recalled_at' + // var row = [0, 'Mazda', 'CX9', true, y2k, y2k, y2k]; + var row = [0, y2k, y2k, 'Mazda', 'CX9', true, y2k, 80000.00]; + //print(row); + var carOpt = CarQuery().deserialize(row); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + //print(car.toJson()); + expect(car.id, '0'); + expect(car.make, 'Mazda'); + expect(car.description, 'CX9'); + expect(car.familyFriendly, true); + expect( + y2k.toIso8601String(), startsWith(car.recalledAt!.toIso8601String())); + expect( + y2k.toIso8601String(), startsWith(car.createdAt!.toIso8601String())); + expect( + y2k.toIso8601String(), startsWith(car.updatedAt!.toIso8601String())); + expect(car.price, 80000.00); + }); + }); + + group('queries', () { + group('selects', () { + test('select all', () async { + var cars = await CarQuery().get(executor); + expect(cars, []); + }); + + group('with data', () { + Car? ferrari; + + setUp(() async { + var query = CarQuery(); + query.values + ..make = 'Ferrarię±' + ..createdAt = y2k + ..updatedAt = y2k + ..description = 'Vroom vroom!' + ..price = 120000.00 + ..familyFriendly = false; + ferrari = (await query.insert(executor)).value; + }); + + test('where clause is applied', () async { + var query = CarQuery()..where!.familyFriendly.isTrue; + var cars = await query.get(executor); + expect(cars, isEmpty); + + var sportsCars = CarQuery()..where!.familyFriendly.isFalse; + cars = await sportsCars.get(executor); + print(cars.map((c) => c.toJson())); + + var car = cars.first; + expect(car.make, ferrari!.make); + expect(car.description, ferrari!.description); + expect(car.familyFriendly, ferrari!.familyFriendly); + expect(car.recalledAt, isNull); + }); + + test('union', () async { + var query1 = CarQuery()..where?.make.like('%Fer%'); + var query2 = CarQuery()..where?.familyFriendly.isTrue; + var query3 = CarQuery()..where?.description.equals('Submarine'); + var union = query1.union(query2).unionAll(query3); + //print(union.compile({})); + var cars = await union.get(executor); + expect(cars, hasLength(1)); + }); + + test('or clause', () async { + var query = CarQuery() + ..where!.make.like('Fer%') + ..orWhere((where) => where + ..familyFriendly.isTrue + ..make.equals('Honda')); + //print(query.compile({})); + var cars = await query.get(executor); + expect(cars, hasLength(1)); + }); + + test('limit obeyed', () async { + var query = CarQuery()..limit(0); + //print(query.compile({})); + var cars = await query.get(executor); + expect(cars, isEmpty); + }); + + test('get one', () async { + var id = int.parse(ferrari!.id!); + var query = CarQuery()..where!.id.equals(id); + var carOpt = await query.getOne(executor); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + expect(car, ferrari); + }); + }); + + test('delete one', () async { + var id = int.parse(ferrari!.id!); + var query = CarQuery()..where!.id.equals(id); + var carOpt = await query.deleteOne(executor); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + expect(car.toJson(), ferrari!.toJson()); + }); + + var cars = await CarQuery().get(executor); + expect(cars, isEmpty); + }); + + test('delete stream', () async { + var query = CarQuery() + ..where!.make.equals('Ferrarię±') + ..orWhere((w) => w.familyFriendly.isTrue); + //print(query.compile({}, preamble: 'DELETE FROM "cars"')); + + var cars = await query.delete(executor); + expect(cars, hasLength(1)); + expect(cars.first.toJson(), ferrari!.toJson()); + }); + + test('update', () async { + var query = CarQuery() + ..where!.id.equals(int.parse(ferrari!.id!)) + ..values.make = 'Hyundai'; + var cars = await query.update(executor); + expect(cars, hasLength(1)); + expect(cars.first.make, 'Hyundai'); + }); + + test('update car', () async { + var cloned = ferrari!.copyWith(make: 'Angel'); + var query = CarQuery()..values.copyFrom(cloned); + var carOpt = await (query.updateOne(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + //print(car.toJson()); + expect(car.toJson(), cloned.toJson()); + }); + }); + }); + }); + + test('insert', () async { + var recalledAt = DateTime.utc(2000, 1, 1, 0, 0, 0, 0, 0); + var query = CarQuery(); + var now = DateTime.now(); + query.values + ..make = 'Honda' + ..description = 'Hello' + ..familyFriendly = true + ..recalledAt = recalledAt + ..createdAt = now + ..updatedAt = now; + var carOpt = await (query.insert(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + var car = carOpt.value; + expect(car.id, isNotNull); + expect(car.make, 'Honda'); + expect(car.description, 'Hello'); + expect(car.familyFriendly, isTrue); + expect( + dateYmdHms.format(car.recalledAt!), dateYmdHms.format(recalledAt)); + expect(dateYmdHms.format(car.createdAt!), dateYmdHms.format(now)); + }); + }); + + test('insert car', () async { + var recalledAt = DateTime.now(); + var beetle = Car( + make: 'Beetle', + description: 'Herbie', + familyFriendly: true, + recalledAt: recalledAt); + var query = CarQuery()..values.copyFrom(beetle); + var carOpt = await (query.insert(executor)); + expect(carOpt.isPresent, true); + carOpt.ifPresent((car) { + //print(car.toJson()); + expect(car.make, beetle.make); + expect(car.description, beetle.description); + expect(car.familyFriendly, beetle.familyFriendly); + expect(dateYmdHms.format(car.recalledAt!), + dateYmdHms.format(beetle.recalledAt!)); + }); + }); + }); +} diff --git a/packages/orm/angel_orm_postgres/test/util.dart b/packages/orm/angel_orm_postgres/test/util.dart new file mode 100644 index 000000000..f1eb1d622 --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/util.dart @@ -0,0 +1,17 @@ +import 'dart:io'; +import 'package:io/ansi.dart'; + +void printSeparator(String title) { + var b = StringBuffer('===${title.toUpperCase()}'); + + int columns = 80; + if (stdout.hasTerminal) { + columns = stdout.terminalColumns - 3; + } + for (var i = b.length; i < columns; i++) { + b.write('='); + } + for (var i = 0; i < 3; i++) { + print(magenta.wrap(b.toString())); + } +} diff --git a/packages/orm/angel_orm_service/CHANGELOG.md b/packages/orm/angel_orm_service/CHANGELOG.md index ad8293d63..f06480d6c 100644 --- a/packages/orm/angel_orm_service/CHANGELOG.md +++ b/packages/orm/angel_orm_service/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/orm/angel_orm_service/lib/angel3_orm_service.dart b/packages/orm/angel_orm_service/lib/angel3_orm_service.dart index 18c79cfc7..0b9c93a2c 100644 --- a/packages/orm/angel_orm_service/lib/angel3_orm_service.dart +++ b/packages/orm/angel_orm_service/lib/angel3_orm_service.dart @@ -33,8 +33,7 @@ class OrmService> {this.idField = 'id', this.allowRemoveAll = false, this.allowQuery = true, - FutureOr Function(RequestContext, ResponseContext)? readData}) - : super(readData: readData); + super.readData}); SqlExpressionBuilder _findBuilder(TQuery query, String name) { return query.where!.expressionBuilders.firstWhere( diff --git a/packages/orm/angel_orm_service/pubspec.yaml b/packages/orm/angel_orm_service/pubspec.yaml index 399fb9e2f..5f97d5c51 100644 --- a/packages/orm/angel_orm_service/pubspec.yaml +++ b/packages/orm/angel_orm_service/pubspec.yaml @@ -4,7 +4,7 @@ description: Service implementation that wraps over Angel3 ORM Query classes. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_service environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_orm: ^8.2.0 diff --git a/packages/orm/angel_orm_test/CHANGELOG.md b/packages/orm/angel_orm_test/CHANGELOG.md index 453601bd8..f4fb6b649 100644 --- a/packages/orm/angel_orm_test/CHANGELOG.md +++ b/packages/orm/angel_orm_test/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release * Take @SerializableField properties into account when generating `Migration` (#98) diff --git a/packages/orm/angel_orm_test/README.md b/packages/orm/angel_orm_test/README.md index 176a740a8..68fc9ebd9 100644 --- a/packages/orm/angel_orm_test/README.md +++ b/packages/orm/angel_orm_test/README.md @@ -5,7 +5,7 @@ [![Discord](https://img.shields.io/discord/1060322353214660698)](https://discord.gg/3X6bxTUdCM) [![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_test/LICENSE) -Common test cases for Angel3 ORM. Reference implmentation of generated ORM files. +**Deprecated as these are test cases for angel3_orm_mysql and angel3_orm_postgres** Common test cases for Angel3 ORM. Reference implmentation of generated ORM files. ## Supported databases diff --git a/packages/orm/angel_orm_test/lib/src/models/book.dart b/packages/orm/angel_orm_test/lib/src/models/book.dart index 91c6b3989..3bc7f79ab 100644 --- a/packages/orm/angel_orm_test/lib/src/models/book.dart +++ b/packages/orm/angel_orm_test/lib/src/models/book.dart @@ -1,4 +1,4 @@ -library angel_orm3.generator.models.book; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/book.g.dart b/packages/orm/angel_orm_test/lib/src/models/book.g.dart index b27b3b0da..319b8b376 100644 --- a/packages/orm/angel_orm_test/lib/src/models/book.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/book.g.dart @@ -78,9 +78,9 @@ class AuthorMigration extends Migration { class BookQuery extends Query { BookQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = BookQueryWhere(this); @@ -139,7 +139,7 @@ class BookQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -148,8 +148,8 @@ class BookQuery extends Query { 'name', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } BookQuery select(List selectedFields) { @@ -311,9 +311,9 @@ class BookQueryValues extends MapQueryValues { class AuthorQuery extends Query { AuthorQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = AuthorQueryWhere(this); @@ -338,15 +338,15 @@ class AuthorQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', 'name', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } AuthorQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/car.dart b/packages/orm/angel_orm_test/lib/src/models/car.dart index 98a3d21b1..49c3ba97e 100644 --- a/packages/orm/angel_orm_test/lib/src/models/car.dart +++ b/packages/orm/angel_orm_test/lib/src/models/car.dart @@ -1,4 +1,4 @@ -library angel_orm3.generator.models.car; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/car.g.dart b/packages/orm/angel_orm_test/lib/src/models/car.g.dart index f73ab2114..6f6bbb990 100644 --- a/packages/orm/angel_orm_test/lib/src/models/car.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/car.g.dart @@ -42,9 +42,9 @@ class CarMigration extends Migration { class CarQuery extends Query { CarQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = CarQueryWhere(this); @@ -69,7 +69,7 @@ class CarQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -80,8 +80,8 @@ class CarQuery extends Query { 'price', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } CarQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/leg.dart b/packages/orm/angel_orm_test/lib/src/models/leg.dart index 0aeee175b..f1e4c5f60 100644 --- a/packages/orm/angel_orm_test/lib/src/models/leg.dart +++ b/packages/orm/angel_orm_test/lib/src/models/leg.dart @@ -1,4 +1,4 @@ -library angel3_orm_generator.test.models.leg; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/leg.g.dart b/packages/orm/angel_orm_test/lib/src/models/leg.g.dart index 60b84dbdb..afdb41088 100644 --- a/packages/orm/angel_orm_test/lib/src/models/leg.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/leg.g.dart @@ -59,9 +59,9 @@ class FootMigration extends Migration { class LegQuery extends Query { LegQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = LegQueryWhere(this); @@ -104,15 +104,15 @@ class LegQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', 'name', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } LegQuery select(List selectedFields) { @@ -234,9 +234,9 @@ class LegQueryValues extends MapQueryValues { class FootQuery extends Query { FootQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = FootQueryWhere(this); @@ -261,7 +261,7 @@ class FootQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -269,8 +269,8 @@ class FootQuery extends Query { 'n_toes', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } FootQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/order.dart b/packages/orm/angel_orm_test/lib/src/models/order.dart index 1c01398d9..595345b2b 100644 --- a/packages/orm/angel_orm_test/lib/src/models/order.dart +++ b/packages/orm/angel_orm_test/lib/src/models/order.dart @@ -1,4 +1,4 @@ -library angel3_orm_generator.test.models.order; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/order.g.dart b/packages/orm/angel_orm_test/lib/src/models/order.g.dart index 9aa92be3e..0d1e6b928 100644 --- a/packages/orm/angel_orm_test/lib/src/models/order.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/order.g.dart @@ -62,9 +62,9 @@ class CustomerMigration extends Migration { class OrderQuery extends Query { OrderQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = OrderQueryWhere(this); @@ -105,7 +105,7 @@ class OrderQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -115,8 +115,8 @@ class OrderQuery extends Query { 'shipper_id', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } OrderQuery select(List selectedFields) { @@ -282,9 +282,9 @@ class OrderQueryValues extends MapQueryValues { class CustomerQuery extends Query { CustomerQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = CustomerQueryWhere(this); @@ -309,14 +309,14 @@ class CustomerQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } CustomerQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/person.dart b/packages/orm/angel_orm_test/lib/src/models/person.dart index 56403bd50..0f7a5ec4f 100644 --- a/packages/orm/angel_orm_test/lib/src/models/person.dart +++ b/packages/orm/angel_orm_test/lib/src/models/person.dart @@ -1,4 +1,4 @@ -library angel_orm3.generator.models.person; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/person.g.dart b/packages/orm/angel_orm_test/lib/src/models/person.g.dart index c68ad1305..d9d7161af 100644 --- a/packages/orm/angel_orm_test/lib/src/models/person.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/person.g.dart @@ -36,9 +36,9 @@ class PersonMigration extends Migration { class PersonQuery extends Query { PersonQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = PersonQueryWhere(this); @@ -63,7 +63,7 @@ class PersonQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -71,8 +71,8 @@ class PersonQuery extends Query { 'age', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } PersonQuery select(List selectedFields) { @@ -199,9 +199,9 @@ class PersonQueryValues extends MapQueryValues { class PersonWithLastOrderQuery extends Query { PersonWithLastOrderQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); expressions['last_order_name'] = 'po.name'; @@ -229,14 +229,14 @@ class PersonWithLastOrderQuery @override List get fields { - const _fields = [ + const fields = [ 'name', 'last_order_name', 'last_order_price', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } PersonWithLastOrderQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/person_order.dart b/packages/orm/angel_orm_test/lib/src/models/person_order.dart index eda4f1b7f..6794d8628 100644 --- a/packages/orm/angel_orm_test/lib/src/models/person_order.dart +++ b/packages/orm/angel_orm_test/lib/src/models/person_order.dart @@ -1,4 +1,4 @@ -library angel3_orm_generator.test.models.person_order; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/person_order.g.dart b/packages/orm/angel_orm_test/lib/src/models/person_order.g.dart index b07ea5f8a..6e30f2e95 100644 --- a/packages/orm/angel_orm_test/lib/src/models/person_order.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/person_order.g.dart @@ -38,9 +38,9 @@ class PersonOrderMigration extends Migration { class PersonOrderQuery extends Query { PersonOrderQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = PersonOrderQueryWhere(this); @@ -65,7 +65,7 @@ class PersonOrderQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -75,8 +75,8 @@ class PersonOrderQuery extends Query { 'deleted', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } PersonOrderQuery select(List selectedFields) { @@ -231,9 +231,9 @@ class PersonOrderQueryValues extends MapQueryValues { class OrderWithPersonInfoQuery extends Query { OrderWithPersonInfoQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); expressions['person_name'] = 'p.name'; @@ -261,7 +261,7 @@ class OrderWithPersonInfoQuery @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -272,8 +272,8 @@ class OrderWithPersonInfoQuery 'person_age', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } OrderWithPersonInfoQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/tree.dart b/packages/orm/angel_orm_test/lib/src/models/tree.dart index 022f402df..dee7eb0d9 100644 --- a/packages/orm/angel_orm_test/lib/src/models/tree.dart +++ b/packages/orm/angel_orm_test/lib/src/models/tree.dart @@ -1,4 +1,4 @@ -library angel3_orm_generator.test.models.tree; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/tree.g.dart b/packages/orm/angel_orm_test/lib/src/models/tree.g.dart index c8db339e1..c86250745 100644 --- a/packages/orm/angel_orm_test/lib/src/models/tree.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/tree.g.dart @@ -59,9 +59,9 @@ class FruitMigration extends Migration { class TreeQuery extends Query { TreeQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = TreeQueryWhere(this); @@ -104,15 +104,15 @@ class TreeQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', 'rings', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } TreeQuery select(List selectedFields) { @@ -288,9 +288,9 @@ class TreeQueryValues extends MapQueryValues { class FruitQuery extends Query { FruitQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = FruitQueryWhere(this); @@ -315,7 +315,7 @@ class FruitQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -323,8 +323,8 @@ class FruitQuery extends Query { 'common_name', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } FruitQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/lib/src/models/user.dart b/packages/orm/angel_orm_test/lib/src/models/user.dart index da28266ed..27195c60c 100644 --- a/packages/orm/angel_orm_test/lib/src/models/user.dart +++ b/packages/orm/angel_orm_test/lib/src/models/user.dart @@ -1,4 +1,4 @@ -library angel3_orm_generator.test.models.user; +library; import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_orm/angel3_orm.dart'; diff --git a/packages/orm/angel_orm_test/lib/src/models/user.g.dart b/packages/orm/angel_orm_test/lib/src/models/user.g.dart index 19143cdca..4a9c359af 100644 --- a/packages/orm/angel_orm_test/lib/src/models/user.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/user.g.dart @@ -106,9 +106,9 @@ class RoleMigration extends Migration { class UserQuery extends Query { UserQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = UserQueryWhere(this); @@ -145,7 +145,7 @@ class UserQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', @@ -154,8 +154,8 @@ class UserQuery extends Query { 'email', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } UserQuery select(List selectedFields) { @@ -361,9 +361,9 @@ class UserQueryValues extends MapQueryValues { class RoleUserQuery extends Query { RoleUserQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = RoleUserQueryWhere(this); @@ -424,13 +424,13 @@ class RoleUserQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'role_id', 'user_id', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } RoleUserQuery select(List selectedFields) { @@ -534,9 +534,9 @@ class RoleUserQueryValues extends MapQueryValues { class RoleQuery extends Query { RoleQuery({ - Query? parent, + super.parent, Set? trampoline, - }) : super(parent: parent) { + }) { trampoline ??= {}; trampoline.add(tableName); _where = RoleQueryWhere(this); @@ -575,15 +575,15 @@ class RoleQuery extends Query { @override List get fields { - const _fields = [ + const fields = [ 'id', 'created_at', 'updated_at', 'name', ]; return _selectedFields.isEmpty - ? _fields - : _fields.where((field) => _selectedFields.contains(field)).toList(); + ? fields + : fields.where((field) => _selectedFields.contains(field)).toList(); } RoleQuery select(List selectedFields) { diff --git a/packages/orm/angel_orm_test/pubspec.yaml b/packages/orm/angel_orm_test/pubspec.yaml index c50d23d21..7154707e4 100644 --- a/packages/orm/angel_orm_test/pubspec.yaml +++ b/packages/orm/angel_orm_test/pubspec.yaml @@ -4,7 +4,7 @@ description: Common tests for Angel3 ORM. Reference implmentation of the generat homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/orm/angel_orm_test environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_migration: ^8.3.0 angel3_model: ^8.2.0 diff --git a/packages/paginate/CHANGELOG.md b/packages/paginate/CHANGELOG.md index 49350f9cf..aa0906b38 100644 --- a/packages/paginate/CHANGELOG.md +++ b/packages/paginate/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/paginate/pubspec.yaml b/packages/paginate/pubspec.yaml index 063ec08a5..b4a81950a 100644 --- a/packages/paginate/pubspec.yaml +++ b/packages/paginate/pubspec.yaml @@ -4,7 +4,7 @@ description: Platform-agnostic pagination library, with custom support for the A homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/paginate environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 dev_dependencies: diff --git a/packages/production/CHANGELOG.md b/packages/production/CHANGELOG.md index f140fe093..3a5f2a542 100644 --- a/packages/production/CHANGELOG.md +++ b/packages/production/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.4.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/production/pubspec.yaml b/packages/production/pubspec.yaml index fbfa23926..7dab6c50a 100644 --- a/packages/production/pubspec.yaml +++ b/packages/production/pubspec.yaml @@ -4,7 +4,7 @@ description: Helpers for concurrency, message-passing, rotating loggers, and oth homepage: https://angel3-framework.web.app repository: https://github.com/dart-backend/angel/tree/master/packages/production environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_container: ^8.0.0 angel3_framework: ^8.4.0 diff --git a/packages/proxy/CHANGELOG.md b/packages/proxy/CHANGELOG.md index 5c6439796..cca2b1c24 100644 --- a/packages/proxy/CHANGELOG.md +++ b/packages/proxy/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/proxy/lib/angel3_proxy.dart b/packages/proxy/lib/angel3_proxy.dart index e375a932d..8eb3c7537 100644 --- a/packages/proxy/lib/angel3_proxy.dart +++ b/packages/proxy/lib/angel3_proxy.dart @@ -1,3 +1,3 @@ -library angel3_proxy; +library; export 'src/proxy_layer.dart'; diff --git a/packages/proxy/pubspec.yaml b/packages/proxy/pubspec.yaml index f82bff980..d9e0fd353 100644 --- a/packages/proxy/pubspec.yaml +++ b/packages/proxy/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel middleware to forward requests to another server (i.e. pub se homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/proxy environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 http: ^1.0.0 diff --git a/packages/redis/CHANGELOG.md b/packages/redis/CHANGELOG.md index 41cd445c2..014684a47 100644 --- a/packages/redis/CHANGELOG.md +++ b/packages/redis/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/redis/pubspec.yaml b/packages/redis/pubspec.yaml index b641db151..459f9a5a2 100644 --- a/packages/redis/pubspec.yaml +++ b/packages/redis/pubspec.yaml @@ -4,7 +4,7 @@ description: An Angel3 service provider for Redis. Works well for caching volati homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/redis environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_http_exception: ^8.0.0 diff --git a/packages/rethinkdb/CHANGELOG.md b/packages/rethinkdb/CHANGELOG.md index e2443696f..55c3791ec 100644 --- a/packages/rethinkdb/CHANGELOG.md +++ b/packages/rethinkdb/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.1.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/route/CHANGELOG.md b/packages/route/CHANGELOG.md index 4d3ef4ae1..a84069ce1 100644 --- a/packages/route/CHANGELOG.md +++ b/packages/route/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/route/lib/angel3_route.dart b/packages/route/lib/angel3_route.dart index fcfc092bc..d2c870180 100644 --- a/packages/route/lib/angel3_route.dart +++ b/packages/route/lib/angel3_route.dart @@ -1,4 +1,4 @@ -library angel3_route; +library; export 'src/middleware_pipeline.dart'; export 'src/router.dart'; diff --git a/packages/route/lib/src/router.dart b/packages/route/lib/src/router.dart index 208253b6b..09ee81a8e 100644 --- a/packages/route/lib/src/router.dart +++ b/packages/route/lib/src/router.dart @@ -1,4 +1,4 @@ -library angel3_route.src.router; +library; import 'dart:async'; import 'package:belatuk_combinator/belatuk_combinator.dart'; diff --git a/packages/route/lib/string_util.dart b/packages/route/lib/string_util.dart index d8eb2282c..b54bbfc93 100644 --- a/packages/route/lib/string_util.dart +++ b/packages/route/lib/string_util.dart @@ -1,5 +1,5 @@ /// Helper functions to performantly transform strings, without `RegExp`. -library angel3_route.string_util; +library; /// Removes leading and trailing occurrences of a pattern from a string. String stripStray(String haystack, String needle) { diff --git a/packages/route/pubspec.yaml b/packages/route/pubspec.yaml index 64a1306b7..d1174caf6 100644 --- a/packages/route/pubspec.yaml +++ b/packages/route/pubspec.yaml @@ -4,7 +4,7 @@ description: A powerful, isomorphic routing library for Dart. It is mainly used homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/route environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: belatuk_combinator: ^5.1.0 string_scanner: ^1.2.0 diff --git a/packages/security/CHANGELOG.md b/packages/security/CHANGELOG.md index f5e1c5072..e198f2b72 100644 --- a/packages/security/CHANGELOG.md +++ b/packages/security/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/security/pubspec.yaml b/packages/security/pubspec.yaml index cf5a769d5..685dc6fda 100644 --- a/packages/security/pubspec.yaml +++ b/packages/security/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 infrastructure for improving security, rate limiting, and mo homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/security environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 crypto: ^3.0.1 diff --git a/packages/sembast/CHANGELOG.md b/packages/sembast/CHANGELOG.md index 2e8f5a32c..913dfc4cb 100644 --- a/packages/sembast/CHANGELOG.md +++ b/packages/sembast/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/sembast/pubspec.yaml b/packages/sembast/pubspec.yaml index cd5e7c07a..ea070faf6 100644 --- a/packages/sembast/pubspec.yaml +++ b/packages/sembast/pubspec.yaml @@ -4,7 +4,7 @@ description: A plugin service that persist data to Sembast for Angel3 framework. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/sembast environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 sembast: ^3.1.1 diff --git a/packages/sembast/test/all_test.dart b/packages/sembast/test/all_test.dart index 49131b7ac..76a79076d 100644 --- a/packages/sembast/test/all_test.dart +++ b/packages/sembast/test/all_test.dart @@ -1,7 +1,6 @@ import 'dart:collection'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_sembast/angel3_sembast.dart'; -import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; import 'package:test/test.dart'; diff --git a/packages/seo/CHANGELOG.md b/packages/seo/CHANGELOG.md index c2352a70a..ad8893d1a 100644 --- a/packages/seo/CHANGELOG.md +++ b/packages/seo/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/seo/pubspec.yaml b/packages/seo/pubspec.yaml index ae26d8abc..3b4d2d1fe 100644 --- a/packages/seo/pubspec.yaml +++ b/packages/seo/pubspec.yaml @@ -4,7 +4,7 @@ description: Helper infrastructure for building SEO-friendly Web backends in Ang homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/angel3/packages/seo environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_static: ^8.2.0 diff --git a/packages/serialize/angel_serialize/CHANGELOG.md b/packages/serialize/angel_serialize/CHANGELOG.md index 67156634c..e4cc60c1b 100644 --- a/packages/serialize/angel_serialize/CHANGELOG.md +++ b/packages/serialize/angel_serialize/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/serialize/angel_serialize/pubspec.yaml b/packages/serialize/angel_serialize/pubspec.yaml index 7e9b5ff1a..b3c422996 100644 --- a/packages/serialize/angel_serialize/pubspec.yaml +++ b/packages/serialize/angel_serialize/pubspec.yaml @@ -4,7 +4,7 @@ description: Static annotations powering Angel3 model serialization. Combine wit homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/serialize/angel_serialize environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_model: ^8.0.0 collection: ^1.17.0 diff --git a/packages/serialize/angel_serialize_generator/CHANGELOG.md b/packages/serialize/angel_serialize_generator/CHANGELOG.md index d18cd76bc..3c8777be1 100644 --- a/packages/serialize/angel_serialize_generator/CHANGELOG.md +++ b/packages/serialize/angel_serialize_generator/CHANGELOG.md @@ -2,9 +2,12 @@ ## 8.4.0 -* Require Dart >= 3.5 -* Updated `lints` to 5.0.0 +* Require Dart >= 3.6 +* Updated `lints` to 5.x +* Updated `source_gen` to 2.x +* Updated `analyzer` to 7.x * Updated dependencies to the latest release +* Updated `ShimFieldImpl` ## 8.3.1 diff --git a/packages/serialize/angel_serialize_generator/example/main.g.dart b/packages/serialize/angel_serialize_generator/example/main.g.dart index 8df50c05a..39aac61a1 100644 --- a/packages/serialize/angel_serialize_generator/example/main.g.dart +++ b/packages/serialize/angel_serialize_generator/example/main.g.dart @@ -75,8 +75,10 @@ class TodoSerializer extends Codec { @override TodoEncoder get encoder => const TodoEncoder(); + @override TodoDecoder get decoder => const TodoDecoder(); + static Todo fromMap(Map map) { return Todo( text: map['text'] as String?, completed: map['completed'] as bool?); diff --git a/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart b/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart index 7dddddeea..aee3b2022 100644 --- a/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart +++ b/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart @@ -1,4 +1,4 @@ -library angel3_serialize_generator; +library; import 'dart:async'; import 'dart:mirrors'; diff --git a/packages/serialize/angel_serialize_generator/lib/build_context.dart b/packages/serialize/angel_serialize_generator/lib/build_context.dart index 00f7e6b17..8ec386481 100644 --- a/packages/serialize/angel_serialize_generator/lib/build_context.dart +++ b/packages/serialize/angel_serialize_generator/lib/build_context.dart @@ -245,8 +245,10 @@ Future buildContext( /// A manually-instantiated [FieldElement]. class ShimFieldImpl extends FieldElementImpl { - @override - final DartType type; + //@override + //final DartType type; - ShimFieldImpl(String name, this.type) : super(name, -1); + ShimFieldImpl(String name, DartType shimFieldType) : super(name, -1) { + type = shimFieldType; + } } diff --git a/packages/serialize/angel_serialize_generator/lib/context.dart b/packages/serialize/angel_serialize_generator/lib/context.dart index 3952b08be..2f4ccdd32 100644 --- a/packages/serialize/angel_serialize_generator/lib/context.dart +++ b/packages/serialize/angel_serialize_generator/lib/context.dart @@ -79,7 +79,7 @@ class BuildContext { fields.firstWhere((f) => f.name == primaryKeyName); bool get importsPackageMeta { - return clazz.library.libraryImports.any((element) { + return clazz.library.definingCompilationUnit.libraryImports.any((element) { var uri = element.uri; if (uri is DirectiveUriWithLibrary) { return uri.relativeUriString == 'package:meta/meta.dart'; diff --git a/packages/serialize/angel_serialize_generator/pubspec.yaml b/packages/serialize/angel_serialize_generator/pubspec.yaml index d36c661e3..55d744b12 100644 --- a/packages/serialize/angel_serialize_generator/pubspec.yaml +++ b/packages/serialize/angel_serialize_generator/pubspec.yaml @@ -4,9 +4,9 @@ description: Angel3 model serialization generators, designed for use with Angel. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/serialize/angel_serialize_generator environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: - analyzer: ^6.5.0 + analyzer: ^7.2.0 angel3_model: ^8.2.0 angel3_serialize: ^8.2.0 belatuk_code_buffer: ^5.2.0 @@ -16,7 +16,7 @@ dependencies: meta: ^1.9.0 path: ^1.9.0 recase: ^4.0.0 - source_gen: ^1.0.0 + source_gen: ^2.0.0 quiver: ^3.0.1 logging: ^1.2.0 dev_dependencies: @@ -24,7 +24,7 @@ dev_dependencies: collection: ^1.17.0 lints: ^5.0.0 test: ^1.24.0 -# dependency_overrides: +#dependency_overrides: # angel3_model: # path: ../../model # angel3_serialize: diff --git a/packages/serialize/angel_serialize_generator/test/models/book.dart b/packages/serialize/angel_serialize_generator/test/models/book.dart index db22c5b42..928dcdebf 100644 --- a/packages/serialize/angel_serialize_generator/test/models/book.dart +++ b/packages/serialize/angel_serialize_generator/test/models/book.dart @@ -1,4 +1,4 @@ -library angel_serialize.test.models.book; +library; import 'package:angel3_serialize/angel3_serialize.dart'; part 'book.g.dart'; diff --git a/packages/serialize/angel_serialize_generator/test/models/book.g.dart b/packages/serialize/angel_serialize_generator/test/models/book.g.dart index 3ab37a3d6..9bca1794c 100644 --- a/packages/serialize/angel_serialize_generator/test/models/book.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/book.g.dart @@ -404,8 +404,10 @@ class BookSerializer extends Codec { @override BookEncoder get encoder => const BookEncoder(); + @override BookDecoder get decoder => const BookDecoder(); + static Book fromMap(Map map) { return Book( id: map['id'] as String?, @@ -500,8 +502,10 @@ class AuthorSerializer extends Codec { @override AuthorEncoder get encoder => const AuthorEncoder(); + @override AuthorDecoder get decoder => const AuthorDecoder(); + static Author fromMap(Map map) { if (map['name'] == null) { throw FormatException("Missing required field 'name' on Author."); @@ -604,8 +608,10 @@ class LibrarySerializer extends Codec { @override LibraryEncoder get encoder => const LibraryEncoder(); + @override LibraryDecoder get decoder => const LibraryDecoder(); + static Library fromMap(Map map) { return Library( id: map['id'] as String?, diff --git a/packages/serialize/angel_serialize_generator/test/models/droid.g.dart b/packages/serialize/angel_serialize_generator/test/models/droid.g.dart index d1a1bd1f8..cdc279f8a 100644 --- a/packages/serialize/angel_serialize_generator/test/models/droid.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/droid.g.dart @@ -112,8 +112,10 @@ class DroidSerializer extends Codec { @override DroidEncoder get encoder => const DroidEncoder(); + @override DroidDecoder get decoder => const DroidDecoder(); + static Droid fromMap(Map map) { return Droid( id: map['id'] as String?, diff --git a/packages/serialize/angel_serialize_generator/test/models/game_pad_button.g.dart b/packages/serialize/angel_serialize_generator/test/models/game_pad_button.g.dart index 5206a1dc4..a77e07c14 100644 --- a/packages/serialize/angel_serialize_generator/test/models/game_pad_button.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/game_pad_button.g.dart @@ -129,8 +129,10 @@ class GamepadButtonSerializer extends Codec { @override GamepadButtonEncoder get encoder => const GamepadButtonEncoder(); + @override GamepadButtonDecoder get decoder => const GamepadButtonDecoder(); + static GamepadButton fromMap(Map map) { return GamepadButton( name: map['name'] as String?, radius: map['radius'] as int?); @@ -176,8 +178,10 @@ class GamepadSerializer extends Codec { @override GamepadEncoder get encoder => const GamepadEncoder(); + @override GamepadDecoder get decoder => const GamepadDecoder(); + static Gamepad fromMap(Map map) { return Gamepad( buttons: map['buttons'] is Iterable diff --git a/packages/serialize/angel_serialize_generator/test/models/goat.g.dart b/packages/serialize/angel_serialize_generator/test/models/goat.g.dart index 91fe0c5e0..1dca55e2b 100644 --- a/packages/serialize/angel_serialize_generator/test/models/goat.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/goat.g.dart @@ -76,8 +76,10 @@ class GoatSerializer extends Codec { @override GoatEncoder get encoder => const GoatEncoder(); + @override GoatDecoder get decoder => const GoatDecoder(); + static Goat fromMap(Map map) { return Goat( integer: map['integer'] as int? ?? 34, diff --git a/packages/serialize/angel_serialize_generator/test/models/has_map.g.dart b/packages/serialize/angel_serialize_generator/test/models/has_map.g.dart index 8f9482d11..133af61ed 100644 --- a/packages/serialize/angel_serialize_generator/test/models/has_map.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/has_map.g.dart @@ -65,8 +65,10 @@ class HasMapSerializer extends Codec { @override HasMapEncoder get encoder => const HasMapEncoder(); + @override HasMapDecoder get decoder => const HasMapDecoder(); + static HasMap fromMap(Map map) { if (map['value'] == null) { throw FormatException("Missing required field 'value' on HasMap."); diff --git a/packages/serialize/angel_serialize_generator/test/models/subclass.g.dart b/packages/serialize/angel_serialize_generator/test/models/subclass.g.dart index a721a9e68..4de614937 100644 --- a/packages/serialize/angel_serialize_generator/test/models/subclass.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/subclass.g.dart @@ -129,8 +129,10 @@ class AnimalSerializer extends Codec { @override AnimalEncoder get encoder => const AnimalEncoder(); + @override AnimalDecoder get decoder => const AnimalDecoder(); + static Animal fromMap(Map map) { if (map['genus'] == null) { throw FormatException("Missing required field 'genus' on Animal."); @@ -184,8 +186,10 @@ class BirdSerializer extends Codec { @override BirdEncoder get encoder => const BirdEncoder(); + @override BirdDecoder get decoder => const BirdDecoder(); + static Bird fromMap(Map map) { if (map['genus'] == null) { throw FormatException("Missing required field 'genus' on Bird."); diff --git a/packages/serialize/angel_serialize_generator/test/models/with_enum.g.dart b/packages/serialize/angel_serialize_generator/test/models/with_enum.g.dart index a13d13b66..b470b22ec 100644 --- a/packages/serialize/angel_serialize_generator/test/models/with_enum.g.dart +++ b/packages/serialize/angel_serialize_generator/test/models/with_enum.g.dart @@ -87,8 +87,10 @@ class WithEnumSerializer extends Codec { @override WithEnumEncoder get encoder => const WithEnumEncoder(); + @override WithEnumDecoder get decoder => const WithEnumDecoder(); + static WithEnum fromMap(Map map) { return WithEnum( type: map['type'] as WithEnumType? ?? WithEnumType.b, diff --git a/packages/shelf/CHANGELOG.md b/packages/shelf/CHANGELOG.md index 75bcee7b9..e76adde47 100644 --- a/packages/shelf/CHANGELOG.md +++ b/packages/shelf/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.0.0-beta.2 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/shelf/pubspec.yaml b/packages/shelf/pubspec.yaml index 20d62805f..4e8489b8c 100644 --- a/packages/shelf/pubspec.yaml +++ b/packages/shelf/pubspec.yaml @@ -4,7 +4,7 @@ description: Shelf interop with Angel3. Use this to wrap existing server code. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/shelf environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' publish_to: none dependencies: angel3_framework: ^8.4.0 diff --git a/packages/static/CHANGELOG.md b/packages/static/CHANGELOG.md index ec6e472e2..155b944f9 100644 --- a/packages/static/CHANGELOG.md +++ b/packages/static/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/static/lib/angel3_static.dart b/packages/static/lib/angel3_static.dart index 4ad551a1b..6b45193cb 100644 --- a/packages/static/lib/angel3_static.dart +++ b/packages/static/lib/angel3_static.dart @@ -1,4 +1,4 @@ -library angel_static; +library; export 'src/cache.dart'; export 'src/virtual_directory.dart'; diff --git a/packages/static/pubspec.yaml b/packages/static/pubspec.yaml index a452195a8..3570c08d6 100644 --- a/packages/static/pubspec.yaml +++ b/packages/static/pubspec.yaml @@ -4,7 +4,7 @@ description: This library provides a virtual directory to serve static files for homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/angel3/packages/static environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 belatuk_range_header: ^6.1.0 diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index da433a015..8e8828abe 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/sync/pubspec.yaml b/packages/sync/pubspec.yaml index 00a7880fd..ff8454f4e 100644 --- a/packages/sync/pubspec.yaml +++ b/packages/sync/pubspec.yaml @@ -4,7 +4,7 @@ description: Easily synchronize and scale WebSockets using belatuk_pub_sub in An homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/angel3/packages/sync environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_websocket: ^8.2.0 diff --git a/packages/test/CHANGELOG.md b/packages/test/CHANGELOG.md index bcb9e5130..4d4458855 100644 --- a/packages/test/CHANGELOG.md +++ b/packages/test/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/test/pubspec.yaml b/packages/test/pubspec.yaml index c52c59924..740d77ff6 100644 --- a/packages/test/pubspec.yaml +++ b/packages/test/pubspec.yaml @@ -4,7 +4,7 @@ description: Testing utility library for the Angel3 framework. Use with package: homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/test environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_client: ^8.2.0 angel3_framework: ^8.4.0 diff --git a/packages/user_agent/angel_user_agent/CHANGELOG.md b/packages/user_agent/angel_user_agent/CHANGELOG.md index 9bc30e0c9..8b36b11c6 100644 --- a/packages/user_agent/angel_user_agent/CHANGELOG.md +++ b/packages/user_agent/angel_user_agent/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/user_agent/angel_user_agent/pubspec.yaml b/packages/user_agent/angel_user_agent/pubspec.yaml index 873899166..7292cc329 100644 --- a/packages/user_agent/angel_user_agent/pubspec.yaml +++ b/packages/user_agent/angel_user_agent/pubspec.yaml @@ -4,7 +4,7 @@ description: Angel3 middleware to parse and inject a User Agent object into requ homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/angel3/packages/user_agent/angel_user_agent environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 user_agent_analyzer: ^5.0.0 diff --git a/packages/validate/CHANGELOG.md b/packages/validate/CHANGELOG.md index 27475f616..7b49376e1 100644 --- a/packages/validate/CHANGELOG.md +++ b/packages/validate/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/validate/lib/angel3_validate.dart b/packages/validate/lib/angel3_validate.dart index 33bb43b43..5cc5cf7fd 100644 --- a/packages/validate/lib/angel3_validate.dart +++ b/packages/validate/lib/angel3_validate.dart @@ -1,5 +1,5 @@ /// Cross-platform validation library based on `matcher`. -library angel3_validate; +library; export 'package:matcher/matcher.dart'; export 'src/context_aware.dart'; diff --git a/packages/validate/lib/server.dart b/packages/validate/lib/server.dart index dea30ad67..39eab8a34 100644 --- a/packages/validate/lib/server.dart +++ b/packages/validate/lib/server.dart @@ -1,5 +1,5 @@ /// Support for using `angel_validate` with the Angel Framework. -library angel3_validate.server; +library; import 'dart:async'; diff --git a/packages/validate/pubspec.yaml b/packages/validate/pubspec.yaml index e56b6d5f8..6b8510127 100644 --- a/packages/validate/pubspec.yaml +++ b/packages/validate/pubspec.yaml @@ -4,7 +4,7 @@ version: 8.3.0 homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/validate environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_framework: ^8.4.0 angel3_http_exception: ^8.0.0 diff --git a/packages/websocket/CHANGELOG.md b/packages/websocket/CHANGELOG.md index 1ec613749..85a4ff8c3 100644 --- a/packages/websocket/CHANGELOG.md +++ b/packages/websocket/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.3.0 -* Require Dart >= 3.5 +* Require Dart >= 3.6 * Updated `lints` to 5.0.0 * Updated dependencies to the latest release diff --git a/packages/websocket/lib/angel3_websocket.dart b/packages/websocket/lib/angel3_websocket.dart index 8b0979c05..5d31ea0a2 100644 --- a/packages/websocket/lib/angel3_websocket.dart +++ b/packages/websocket/lib/angel3_websocket.dart @@ -1,5 +1,5 @@ /// WebSocket plugin for Angel. -library angel3_websocket; +library; /// A notification from the server that something has occurred. class WebSocketEvent { diff --git a/packages/websocket/lib/browser.dart b/packages/websocket/lib/browser.dart index cd280862b..9111f3b5f 100644 --- a/packages/websocket/lib/browser.dart +++ b/packages/websocket/lib/browser.dart @@ -1,5 +1,5 @@ /// Browser WebSocket client library for the Angel framework. -library angel3_websocket.browser; +library; import 'dart:async'; import 'dart:html'; diff --git a/packages/websocket/lib/flutter.dart b/packages/websocket/lib/flutter.dart index 94210e4e1..bca22caa9 100644 --- a/packages/websocket/lib/flutter.dart +++ b/packages/websocket/lib/flutter.dart @@ -1,5 +1,5 @@ /// Flutter-compatible WebSocket client library for the Angel framework. -library angel_websocket.flutter; +library; import 'dart:async'; import 'dart:io'; diff --git a/packages/websocket/lib/io.dart b/packages/websocket/lib/io.dart index aba5fc4bd..446fe5923 100644 --- a/packages/websocket/lib/io.dart +++ b/packages/websocket/lib/io.dart @@ -1,5 +1,5 @@ /// Command-line WebSocket client library for the Angel framework. -library angel3_websocket.io; +library; import 'dart:async'; import 'dart:io'; diff --git a/packages/websocket/lib/server.dart b/packages/websocket/lib/server.dart index b66c7a525..c45edc510 100644 --- a/packages/websocket/lib/server.dart +++ b/packages/websocket/lib/server.dart @@ -1,5 +1,5 @@ /// Server-side support for WebSockets. -library angel3_websocket.server; +library; import 'dart:async'; import 'dart:convert'; diff --git a/packages/websocket/pubspec.yaml b/packages/websocket/pubspec.yaml index ca203e63c..63e9a423f 100644 --- a/packages/websocket/pubspec.yaml +++ b/packages/websocket/pubspec.yaml @@ -4,7 +4,7 @@ description: This library provides WebSockets support for Angel3 framework. homepage: https://angel3-framework.web.app/ repository: https://github.com/dart-backend/angel/tree/master/packages/websocket environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: angel3_auth: ^8.2.0 angel3_client: ^8.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index f68bd0d40..4b31d4399 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: angel environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: melos: ^6.0.0