From f863ba4e0e7d877c843ce5085ddfd399da10044b Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Wed, 11 Jan 2023 20:02:53 +0100 Subject: [PATCH 1/9] Fix CastError --- lib/src/builder/elements/table_element.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/builder/elements/table_element.dart b/lib/src/builder/elements/table_element.dart index c4464e8..239b7ff 100644 --- a/lib/src/builder/elements/table_element.dart +++ b/lib/src/builder/elements/table_element.dart @@ -89,8 +89,9 @@ class TableElement { : null; void prepareColumns() { - final allFields = - element.fields.followedBy(element.allSupertypes.expand((t) => t.isDartCoreObject ? [] : t.element.fields)); + final allFields = element.fields + .cast() + .followedBy(element.allSupertypes.expand((t) => t.isDartCoreObject ? [] : t.element.fields)); for (var param in allFields) { if (columns.any((c) => c.parameter == param)) { @@ -124,7 +125,8 @@ class TableElement { } if (selfHasKey && otherHasKey && !selfIsList && !otherIsList) { - var eitherNullable = param.type.nullabilitySuffix != NullabilitySuffix.none || otherParam!.type.nullabilitySuffix != NullabilitySuffix.none; + var eitherNullable = param.type.nullabilitySuffix != NullabilitySuffix.none || + otherParam!.type.nullabilitySuffix != NullabilitySuffix.none; if (!eitherNullable) { throw 'Model ${otherBuilder.element.name} cannot have a one-to-one relation to model ${element.name} with ' 'both sides being non-nullable. At least one side has to be nullable, to insert one model before the other.\n' From 7b827868d2e496c2da6093b2f956fc4b14b5337b Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Wed, 11 Jan 2023 20:05:28 +0100 Subject: [PATCH 2/9] Add prefix to generated repositories --- .../builder/generators/repository_generator.dart | 5 ++++- lib/src/builder/schema.dart | 14 +++++++++----- pubspec.yaml | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/src/builder/generators/repository_generator.dart b/lib/src/builder/generators/repository_generator.dart index c420614..00fbd1a 100644 --- a/lib/src/builder/generators/repository_generator.dart +++ b/lib/src/builder/generators/repository_generator.dart @@ -1,3 +1,6 @@ +import 'package:recase/recase.dart'; +import 'package:path/path.dart' as p; + import '../schema.dart'; import '../elements/table_element.dart'; import 'insert_generator.dart'; @@ -7,7 +10,7 @@ import 'view_generator.dart'; class RepositoryGenerator { String generateRepositories(AssetState state) { return ''' - extension Repositories on Database { + extension ${p.withoutExtension(state.filename).pascalCase}Repositories on Database { ${state.tables.values.map((b) => ' ${b.element.name}Repository get ${b.repoName} => ${b.element.name}Repository._(this);\n').join()} } diff --git a/lib/src/builder/schema.dart b/lib/src/builder/schema.dart index 995d88b..5863707 100644 --- a/lib/src/builder/schema.dart +++ b/lib/src/builder/schema.dart @@ -1,5 +1,5 @@ - import 'package:analyzer/dart/element/element.dart'; +import 'package:path/path.dart' as p; import 'package:build/build.dart'; import 'utils.dart'; @@ -13,11 +13,12 @@ class SchemaState { bool _didFinalize = false; Map get tables => _assets.values.map((a) => a.tables).reduce((a, b) => {...a, ...b}); - Map get joinTables => _assets.values.map((a) => a.joinTables).reduce((a, b) => {...a, ...b}); + Map get joinTables => + _assets.values.map((a) => a.joinTables).reduce((a, b) => {...a, ...b}); AssetState createForAsset(AssetId assetId) { assert(!_didFinalize, 'Schema was already finalized.'); - var asset = AssetState(); + var asset = AssetState(p.basename(assetId.path)); return _assets[assetId] = asset; } @@ -34,12 +35,15 @@ class SchemaState { _didFinalize = true; } } - } class AssetState { + final String filename; + Map tables = {}; Map joinTables = {}; + + AssetState(this.filename); } class BuilderState { @@ -48,4 +52,4 @@ class BuilderState { AssetState asset; BuilderState(this.options, this.schema, this.asset); -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index 179eb86..6f8cdba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: dart_style: ^2.2.4 path: ^1.8.2 postgres: ^2.5.1 + recase: ^4.1.0 source_gen: ^1.2.3 yaml: ^3.1.1 From 1c3dcf34468b949fabb97ffc52df153fa2348d69 Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Wed, 11 Jan 2023 20:09:11 +0100 Subject: [PATCH 3/9] Re-run generator on example --- example/lib/model.schema.dart | 2 +- example/lib/models/account.schema.dart | 54 ++++++++++++------------- example/lib/models/address.schema.dart | 20 ++++----- example/lib/models/company.schema.dart | 56 +++++++++++++------------- example/lib/models/invoice.schema.dart | 20 ++++----- example/lib/models/party.schema.dart | 26 ++++++------ 6 files changed, 89 insertions(+), 89 deletions(-) diff --git a/example/lib/model.schema.dart b/example/lib/model.schema.dart index c078fba..ce09ee7 100644 --- a/example/lib/model.schema.dart +++ b/example/lib/model.schema.dart @@ -1,6 +1,6 @@ part of 'model.dart'; -extension Repositories on Database { +extension ModelRepositories on Database { ARepository get as => ARepository._(this); BRepository get bs => BRepository._(this); } diff --git a/example/lib/models/account.schema.dart b/example/lib/models/account.schema.dart index 9b982e1..d582239 100644 --- a/example/lib/models/account.schema.dart +++ b/example/lib/models/account.schema.dart @@ -1,6 +1,6 @@ part of 'account.dart'; -extension Repositories on Database { +extension AccountRepositories on Database { AccountRepository get accounts => AccountRepository._(this); } @@ -66,14 +66,14 @@ class _AccountRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "accounts" ( "id", "first_name", "last_name", "location", "company_id" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(autoIncrements[requests.indexOf(r)]['id'])}, ${values.add(r.firstName)}, ${values.add(r.lastName)}, ${values.add(LatLngConverter().tryEncode(r.location))}, ${values.add(r.companyId)} )').join(', ')}\n', + 'INSERT INTO "accounts" ( "company_id", "id", "first_name", "last_name", "location" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(autoIncrements[requests.indexOf(r)]['id'])}, ${values.add(r.firstName)}, ${values.add(r.lastName)}, ${values.add(LatLngConverter().tryEncode(r.location))} )').join(', ')}\n', values.values, ); await db.billingAddresses.insertMany(requests.where((r) => r.billingAddress != null).map((r) { return BillingAddressInsertRequest( - accountId: TextEncoder.i.decode(autoIncrements[requests.indexOf(r)]['id']), companyId: null, + accountId: TextEncoder.i.decode(autoIncrements[requests.indexOf(r)]['id']), city: r.billingAddress!.city, postcode: r.billingAddress!.postcode, name: r.billingAddress!.name, @@ -89,9 +89,9 @@ class _AccountRepository extends BaseRepository var values = QueryValues(); await db.query( 'UPDATE "accounts"\n' - 'SET "first_name" = COALESCE(UPDATED."first_name"::text, "accounts"."first_name"), "last_name" = COALESCE(UPDATED."last_name"::text, "accounts"."last_name"), "location" = COALESCE(UPDATED."location"::point, "accounts"."location"), "company_id" = COALESCE(UPDATED."company_id"::text, "accounts"."company_id")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.firstName)}, ${values.add(r.lastName)}, ${values.add(LatLngConverter().tryEncode(r.location))}, ${values.add(r.companyId)} )').join(', ')} )\n' - 'AS UPDATED("id", "first_name", "last_name", "location", "company_id")\n' + 'SET "company_id" = COALESCE(UPDATED."company_id"::text, "accounts"."company_id"), "first_name" = COALESCE(UPDATED."first_name"::text, "accounts"."first_name"), "last_name" = COALESCE(UPDATED."last_name"::text, "accounts"."last_name"), "location" = COALESCE(UPDATED."location"::point, "accounts"."location")\n' + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(r.id)}, ${values.add(r.firstName)}, ${values.add(r.lastName)}, ${values.add(LatLngConverter().tryEncode(r.location))} )').join(', ')} )\n' + 'AS UPDATED("company_id", "id", "first_name", "last_name", "location")\n' 'WHERE "accounts"."id" = UPDATED."id"', values.values, ); @@ -108,36 +108,36 @@ class _AccountRepository extends BaseRepository class AccountInsertRequest { AccountInsertRequest({ + this.companyId, required this.firstName, required this.lastName, required this.location, this.billingAddress, - this.companyId, }); + String? companyId; String firstName; String lastName; LatLng location; BillingAddress? billingAddress; - String? companyId; } class AccountUpdateRequest { AccountUpdateRequest({ + this.companyId, required this.id, this.firstName, this.lastName, this.location, this.billingAddress, - this.companyId, }); + String? companyId; int id; String? firstName; String? lastName; LatLng? location; BillingAddress? billingAddress; - String? companyId; } class FullAccountViewQueryable extends KeyedViewQueryable { @@ -149,8 +149,10 @@ class FullAccountViewQueryable extends KeyedViewQueryable @override String get query => - 'SELECT "accounts".*, "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress", row_to_json("company".*) as "company"' + 'SELECT "accounts".*, row_to_json("company".*) as "company", "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress"' 'FROM "accounts"' + 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' + 'ON "accounts"."company_id" = "company"."id"' 'LEFT JOIN (' ' SELECT "invoices"."account_id",' ' to_jsonb(array_agg("invoices".*)) as data' @@ -168,27 +170,26 @@ class FullAccountViewQueryable extends KeyedViewQueryable ') "parties"' 'ON "accounts"."id" = "parties"."account_id"' 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' - 'ON "accounts"."id" = "billingAddress"."account_id"' - 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' - 'ON "accounts"."company_id" = "company"."id"'; + 'ON "accounts"."id" = "billingAddress"."account_id"'; @override String get tableAlias => 'accounts'; @override FullAccountView decode(TypedMap map) => FullAccountView( + company: map.getOpt('company', MemberCompanyViewQueryable().decoder), invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], id: map.get('id', TextEncoder.i.decode), firstName: map.get('first_name', TextEncoder.i.decode), lastName: map.get('last_name', TextEncoder.i.decode), location: map.get('location', LatLngConverter().decode), - billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), - company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); + billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder)); } class FullAccountView { FullAccountView({ + this.company, required this.invoices, required this.parties, required this.id, @@ -196,9 +197,9 @@ class FullAccountView { required this.lastName, required this.location, this.billingAddress, - this.company, }); + final MemberCompanyView? company; final List invoices; final List parties; final int id; @@ -206,7 +207,6 @@ class FullAccountView { final String lastName; final LatLng location; final BillingAddress? billingAddress; - final MemberCompanyView? company; } class UserAccountViewQueryable extends KeyedViewQueryable { @@ -218,8 +218,10 @@ class UserAccountViewQueryable extends KeyedViewQueryable @override String get query => - 'SELECT "accounts".*, "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress", row_to_json("company".*) as "company"' + 'SELECT "accounts".*, row_to_json("company".*) as "company", "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress"' 'FROM "accounts"' + 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' + 'ON "accounts"."company_id" = "company"."id"' 'LEFT JOIN (' ' SELECT "invoices"."account_id",' ' to_jsonb(array_agg("invoices".*)) as data' @@ -237,27 +239,26 @@ class UserAccountViewQueryable extends KeyedViewQueryable ') "parties"' 'ON "accounts"."id" = "parties"."account_id"' 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' - 'ON "accounts"."id" = "billingAddress"."account_id"' - 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' - 'ON "accounts"."company_id" = "company"."id"'; + 'ON "accounts"."id" = "billingAddress"."account_id"'; @override String get tableAlias => 'accounts'; @override UserAccountView decode(TypedMap map) => UserAccountView( + company: map.getOpt('company', MemberCompanyViewQueryable().decoder), invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], id: map.get('id', TextEncoder.i.decode), firstName: map.get('first_name', TextEncoder.i.decode), lastName: map.get('last_name', TextEncoder.i.decode), location: map.get('location', LatLngConverter().decode), - billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), - company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); + billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder)); } class UserAccountView { UserAccountView({ + this.company, required this.invoices, required this.parties, required this.id, @@ -265,9 +266,9 @@ class UserAccountView { required this.lastName, required this.location, this.billingAddress, - this.company, }); + final MemberCompanyView? company; final List invoices; final List parties; final int id; @@ -275,7 +276,6 @@ class UserAccountView { final String lastName; final LatLng location; final BillingAddress? billingAddress; - final MemberCompanyView? company; } class CompanyAccountViewQueryable extends KeyedViewQueryable { diff --git a/example/lib/models/address.schema.dart b/example/lib/models/address.schema.dart index 3a9907b..ca0bf73 100644 --- a/example/lib/models/address.schema.dart +++ b/example/lib/models/address.schema.dart @@ -1,6 +1,6 @@ part of 'address.dart'; -extension Repositories on Database { +extension AddressRepositories on Database { BillingAddressRepository get billingAddresses => BillingAddressRepository._(this); } @@ -30,8 +30,8 @@ class _BillingAddressRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "billing_addresses" ( "account_id", "company_id", "city", "postcode", "name", "street" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(r.accountId)}, ${values.add(r.companyId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')}\n', + 'INSERT INTO "billing_addresses" ( "company_id", "account_id", "city", "postcode", "name", "street" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(r.accountId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')}\n', values.values, ); } @@ -43,9 +43,9 @@ class _BillingAddressRepository extends BaseRepository await db.query( 'UPDATE "billing_addresses"\n' 'SET "city" = COALESCE(UPDATED."city"::text, "billing_addresses"."city"), "postcode" = COALESCE(UPDATED."postcode"::text, "billing_addresses"."postcode"), "name" = COALESCE(UPDATED."name"::text, "billing_addresses"."name"), "street" = COALESCE(UPDATED."street"::text, "billing_addresses"."street")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.accountId)}, ${values.add(r.companyId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')} )\n' - 'AS UPDATED("account_id", "company_id", "city", "postcode", "name", "street")\n' - 'WHERE "billing_addresses"."account_id" = UPDATED."account_id" AND "billing_addresses"."company_id" = UPDATED."company_id"', + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(r.accountId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')} )\n' + 'AS UPDATED("company_id", "account_id", "city", "postcode", "name", "street")\n' + 'WHERE "billing_addresses"."company_id" = UPDATED."company_id" AND "billing_addresses"."account_id" = UPDATED."account_id"', values.values, ); } @@ -53,16 +53,16 @@ class _BillingAddressRepository extends BaseRepository class BillingAddressInsertRequest { BillingAddressInsertRequest({ - this.accountId, this.companyId, + this.accountId, required this.city, required this.postcode, required this.name, required this.street, }); - int? accountId; String? companyId; + int? accountId; String city; String postcode; String name; @@ -71,16 +71,16 @@ class BillingAddressInsertRequest { class BillingAddressUpdateRequest { BillingAddressUpdateRequest({ - this.accountId, this.companyId, + this.accountId, this.city, this.postcode, this.name, this.street, }); - int? accountId; String? companyId; + int? accountId; String? city; String? postcode; String? name; diff --git a/example/lib/models/company.schema.dart b/example/lib/models/company.schema.dart index f457bd2..0b90097 100644 --- a/example/lib/models/company.schema.dart +++ b/example/lib/models/company.schema.dart @@ -1,6 +1,6 @@ part of 'company.dart'; -extension Repositories on Database { +extension CompanyRepositories on Database { CompanyRepository get companies => CompanyRepository._(this); } @@ -58,7 +58,7 @@ class _CompanyRepository extends BaseRepository ); await db.billingAddresses.insertMany(requests.expand((r) { return r.addresses.map((rr) => BillingAddressInsertRequest( - accountId: null, companyId: r.id, city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street)); + companyId: r.id, accountId: null, city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street)); }).toList()); } @@ -114,9 +114,23 @@ class FullCompanyViewQueryable extends KeyedViewQueryable - 'SELECT "companies".*, "invoices"."data" as "invoices", "parties"."data" as "parties", "members"."data" as "members", "addresses"."data" as "addresses"' + 'SELECT "companies".*, "addresses"."data" as "addresses", "members"."data" as "members", "invoices"."data" as "invoices", "parties"."data" as "parties"' 'FROM "companies"' 'LEFT JOIN (' + ' SELECT "billing_addresses"."company_id",' + ' to_jsonb(array_agg("billing_addresses".*)) as data' + ' FROM (${BillingAddressQueryable().query}) "billing_addresses"' + ' GROUP BY "billing_addresses"."company_id"' + ') "addresses"' + 'ON "companies"."id" = "addresses"."company_id"' + 'LEFT JOIN (' + ' SELECT "accounts"."company_id",' + ' to_jsonb(array_agg("accounts".*)) as data' + ' FROM (${CompanyAccountViewQueryable().query}) "accounts"' + ' GROUP BY "accounts"."company_id"' + ') "members"' + 'ON "companies"."id" = "members"."company_id"' + 'LEFT JOIN (' ' SELECT "invoices"."company_id",' ' to_jsonb(array_agg("invoices".*)) as data' ' FROM (${OwnerInvoiceViewQueryable().query}) "invoices"' @@ -129,51 +143,37 @@ class FullCompanyViewQueryable extends KeyedViewQueryable 'companies'; @override FullCompanyView decode(TypedMap map) => FullCompanyView( - invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], - parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const [], - members: map.getListOpt('members', CompanyAccountViewQueryable().decoder) ?? const [], id: map.get('id', TextEncoder.i.decode), name: map.get('name', TextEncoder.i.decode), - addresses: map.getListOpt('addresses', BillingAddressQueryable().decoder) ?? const []); + addresses: map.getListOpt('addresses', BillingAddressQueryable().decoder) ?? const [], + members: map.getListOpt('members', CompanyAccountViewQueryable().decoder) ?? const [], + invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], + parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const []); } class FullCompanyView { FullCompanyView({ - required this.invoices, - required this.parties, - required this.members, required this.id, required this.name, required this.addresses, + required this.members, + required this.invoices, + required this.parties, }); - final List invoices; - final List parties; - final List members; final String id; final String name; final List addresses; + final List members; + final List invoices; + final List parties; } class MemberCompanyViewQueryable extends KeyedViewQueryable { diff --git a/example/lib/models/invoice.schema.dart b/example/lib/models/invoice.schema.dart index 5fbd13a..a4f8ed2 100644 --- a/example/lib/models/invoice.schema.dart +++ b/example/lib/models/invoice.schema.dart @@ -1,6 +1,6 @@ part of 'invoice.dart'; -extension Repositories on Database { +extension InvoiceRepositories on Database { InvoiceRepository get invoices => InvoiceRepository._(this); } @@ -40,8 +40,8 @@ class _InvoiceRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "invoices" ( "id", "title", "invoice_id", "account_id", "company_id" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.title)}, ${values.add(r.invoiceId)}, ${values.add(r.accountId)}, ${values.add(r.companyId)} )').join(', ')}\n', + 'INSERT INTO "invoices" ( "company_id", "id", "title", "invoice_id", "account_id" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(r.id)}, ${values.add(r.title)}, ${values.add(r.invoiceId)}, ${values.add(r.accountId)} )').join(', ')}\n', values.values, ); } @@ -52,9 +52,9 @@ class _InvoiceRepository extends BaseRepository var values = QueryValues(); await db.query( 'UPDATE "invoices"\n' - 'SET "title" = COALESCE(UPDATED."title"::text, "invoices"."title"), "invoice_id" = COALESCE(UPDATED."invoice_id"::text, "invoices"."invoice_id"), "account_id" = COALESCE(UPDATED."account_id"::int8, "invoices"."account_id"), "company_id" = COALESCE(UPDATED."company_id"::text, "invoices"."company_id")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.title)}, ${values.add(r.invoiceId)}, ${values.add(r.accountId)}, ${values.add(r.companyId)} )').join(', ')} )\n' - 'AS UPDATED("id", "title", "invoice_id", "account_id", "company_id")\n' + 'SET "company_id" = COALESCE(UPDATED."company_id"::text, "invoices"."company_id"), "title" = COALESCE(UPDATED."title"::text, "invoices"."title"), "invoice_id" = COALESCE(UPDATED."invoice_id"::text, "invoices"."invoice_id"), "account_id" = COALESCE(UPDATED."account_id"::int8, "invoices"."account_id")\n' + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.companyId)}, ${values.add(r.id)}, ${values.add(r.title)}, ${values.add(r.invoiceId)}, ${values.add(r.accountId)} )').join(', ')} )\n' + 'AS UPDATED("company_id", "id", "title", "invoice_id", "account_id")\n' 'WHERE "invoices"."id" = UPDATED."id"', values.values, ); @@ -63,34 +63,34 @@ class _InvoiceRepository extends BaseRepository class InvoiceInsertRequest { InvoiceInsertRequest({ + this.companyId, required this.id, required this.title, required this.invoiceId, this.accountId, - this.companyId, }); + String? companyId; String id; String title; String invoiceId; int? accountId; - String? companyId; } class InvoiceUpdateRequest { InvoiceUpdateRequest({ + this.companyId, required this.id, this.title, this.invoiceId, this.accountId, - this.companyId, }); + String? companyId; String id; String? title; String? invoiceId; int? accountId; - String? companyId; } class OwnerInvoiceViewQueryable extends KeyedViewQueryable { diff --git a/example/lib/models/party.schema.dart b/example/lib/models/party.schema.dart index 2316680..5d212a2 100644 --- a/example/lib/models/party.schema.dart +++ b/example/lib/models/party.schema.dart @@ -1,6 +1,6 @@ part of 'party.dart'; -extension Repositories on Database { +extension PartyRepositories on Database { PartyRepository get parties => PartyRepository._(this); } @@ -52,8 +52,8 @@ class _PartyRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "parties" ( "id", "name", "sponsor_id", "date" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.name)}, ${values.add(r.sponsorId)}, ${values.add(r.date)} )').join(', ')}\n', + 'INSERT INTO "parties" ( "sponsor_id", "id", "name", "date" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.sponsorId)}, ${values.add(r.id)}, ${values.add(r.name)}, ${values.add(r.date)} )').join(', ')}\n', values.values, ); } @@ -64,9 +64,9 @@ class _PartyRepository extends BaseRepository var values = QueryValues(); await db.query( 'UPDATE "parties"\n' - 'SET "name" = COALESCE(UPDATED."name"::text, "parties"."name"), "sponsor_id" = COALESCE(UPDATED."sponsor_id"::text, "parties"."sponsor_id"), "date" = COALESCE(UPDATED."date"::int8, "parties"."date")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.name)}, ${values.add(r.sponsorId)}, ${values.add(r.date)} )').join(', ')} )\n' - 'AS UPDATED("id", "name", "sponsor_id", "date")\n' + 'SET "sponsor_id" = COALESCE(UPDATED."sponsor_id"::text, "parties"."sponsor_id"), "name" = COALESCE(UPDATED."name"::text, "parties"."name"), "date" = COALESCE(UPDATED."date"::int8, "parties"."date")\n' + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.sponsorId)}, ${values.add(r.id)}, ${values.add(r.name)}, ${values.add(r.date)} )').join(', ')} )\n' + 'AS UPDATED("sponsor_id", "id", "name", "date")\n' 'WHERE "parties"."id" = UPDATED."id"', values.values, ); @@ -75,29 +75,29 @@ class _PartyRepository extends BaseRepository class PartyInsertRequest { PartyInsertRequest({ + this.sponsorId, required this.id, required this.name, - this.sponsorId, required this.date, }); + String? sponsorId; String id; String name; - String? sponsorId; int date; } class PartyUpdateRequest { PartyUpdateRequest({ + this.sponsorId, required this.id, this.name, - this.sponsorId, this.date, }); + String? sponsorId; String id; String? name; - String? sponsorId; int? date; } @@ -119,23 +119,23 @@ class GuestPartyViewQueryable extends KeyedViewQueryable @override GuestPartyView decode(TypedMap map) => GuestPartyView( + sponsor: map.getOpt('sponsor', MemberCompanyViewQueryable().decoder), id: map.get('id', TextEncoder.i.decode), name: map.get('name', TextEncoder.i.decode), - sponsor: map.getOpt('sponsor', MemberCompanyViewQueryable().decoder), date: map.get('date', TextEncoder.i.decode)); } class GuestPartyView { GuestPartyView({ + this.sponsor, required this.id, required this.name, - this.sponsor, required this.date, }); + final MemberCompanyView? sponsor; final String id; final String name; - final MemberCompanyView? sponsor; final int date; } From ddca4df68f45a60ea65b481e0049daf21396679a Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Wed, 11 Jan 2023 20:58:15 +0100 Subject: [PATCH 4/9] Don't try to generate schema for non-library files --- lib/src/builder/builders/output_builder.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/builder/builders/output_builder.dart b/lib/src/builder/builders/output_builder.dart index 1c0103b..63b5500 100644 --- a/lib/src/builder/builders/output_builder.dart +++ b/lib/src/builder/builders/output_builder.dart @@ -14,6 +14,10 @@ abstract class OutputBuilder implements Builder { @override Future build(BuildStep buildStep) async { + if (!await buildStep.resolver.isLibrary(buildStep.inputId)) { + return; + } + await buildStep.inputLibrary; try { From 45a93209bba134bd5ccf67afb59386e3d6ee7058 Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Thu, 12 Jan 2023 16:11:30 +0100 Subject: [PATCH 5/9] Don't error when insert/update constructors contain no parameters --- lib/src/builder/generators/insert_generator.dart | 4 +++- lib/src/builder/generators/update_generator.dart | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/builder/generators/insert_generator.dart b/lib/src/builder/generators/insert_generator.dart index 3e0c8af..35cb253 100644 --- a/lib/src/builder/generators/insert_generator.dart +++ b/lib/src/builder/generators/insert_generator.dart @@ -147,10 +147,12 @@ class InsertGenerator { } } + var constructorParameters = + requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', '); return ''' ${table.annotateWith ?? ''} class $requestClassName { - $requestClassName({${requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', ')},}); + $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters,}' : ''}); ${requestFields.map((f) => '${f.key} ${f.value};').join('\n')} } diff --git a/lib/src/builder/generators/update_generator.dart b/lib/src/builder/generators/update_generator.dart index b10635a..92f772b 100644 --- a/lib/src/builder/generators/update_generator.dart +++ b/lib/src/builder/generators/update_generator.dart @@ -72,7 +72,6 @@ class UpdateGenerator { return '\${values.add(${c.converter!.toSource()}.tryEncode(r.${c.paramName}))}'; } else { return '\${values.add(r.${c.paramName})}'; - } } @@ -144,10 +143,13 @@ class UpdateGenerator { } } + final constructorParameters = + requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', '); + return ''' ${table.annotateWith ?? ''} class $requestClassName { - $requestClassName({${requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', ')},}); + $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters,}' : ''}); ${requestFields.map((f) => '${f.key} ${f.value};').join('\n')} } From 9fef1ef5488eeb3ee4661829b38b0aa9517e24ee Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Sun, 15 Jan 2023 09:43:08 +0100 Subject: [PATCH 6/9] change decoding syntax --- example/lib/model.schema.dart | 4 ++-- example/lib/models/account.schema.dart | 18 +++++++++--------- example/lib/models/address.schema.dart | 5 +---- example/lib/models/company.schema.dart | 8 ++++---- example/lib/models/invoice.schema.dart | 6 ++---- example/lib/models/party.schema.dart | 12 +++++------- lib/src/builder/generators/view_generator.dart | 10 +++++----- lib/src/internals/text_encoder.dart | 18 +++++++++--------- 8 files changed, 37 insertions(+), 44 deletions(-) diff --git a/example/lib/model.schema.dart b/example/lib/model.schema.dart index c078fba..a092926 100644 --- a/example/lib/model.schema.dart +++ b/example/lib/model.schema.dart @@ -170,7 +170,7 @@ class AQueryable extends KeyedViewQueryable { String get tableAlias => 'as'; @override - A decode(TypedMap map) => AView(id: map.get('id', TextEncoder.i.decode), b: map.get('b', BQueryable().decoder)); + A decode(TypedMap map) => AView(id: map.get('id'), b: map.get('b', BQueryable().decoder)); } class AView implements A { @@ -202,7 +202,7 @@ class BQueryable extends KeyedViewQueryable { String get tableAlias => 'bs'; @override - B decode(TypedMap map) => BView(a: map.getOpt('a', AQueryable().decoder), id: map.get('id', TextEncoder.i.decode)); + B decode(TypedMap map) => BView(a: map.getOpt('a', AQueryable().decoder), id: map.get('id')); } class BView implements B { diff --git a/example/lib/models/account.schema.dart b/example/lib/models/account.schema.dart index 9b982e1..9b491e8 100644 --- a/example/lib/models/account.schema.dart +++ b/example/lib/models/account.schema.dart @@ -179,9 +179,9 @@ class FullAccountViewQueryable extends KeyedViewQueryable FullAccountView decode(TypedMap map) => FullAccountView( invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], - id: map.get('id', TextEncoder.i.decode), - firstName: map.get('first_name', TextEncoder.i.decode), - lastName: map.get('last_name', TextEncoder.i.decode), + id: map.get('id'), + firstName: map.get('first_name'), + lastName: map.get('last_name'), location: map.get('location', LatLngConverter().decode), billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); @@ -248,9 +248,9 @@ class UserAccountViewQueryable extends KeyedViewQueryable UserAccountView decode(TypedMap map) => UserAccountView( invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], - id: map.get('id', TextEncoder.i.decode), - firstName: map.get('first_name', TextEncoder.i.decode), - lastName: map.get('last_name', TextEncoder.i.decode), + id: map.get('id'), + firstName: map.get('first_name'), + lastName: map.get('last_name'), location: map.get('location', LatLngConverter().decode), billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); @@ -305,9 +305,9 @@ class CompanyAccountViewQueryable extends KeyedViewQueryable CompanyAccountView( parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const [], - id: map.get('id', TextEncoder.i.decode), - firstName: map.get('first_name', TextEncoder.i.decode), - lastName: map.get('last_name', TextEncoder.i.decode), + id: map.get('id'), + firstName: map.get('first_name'), + lastName: map.get('last_name'), location: map.get('location', LatLngConverter().decode)); } diff --git a/example/lib/models/address.schema.dart b/example/lib/models/address.schema.dart index 3a9907b..fa17cba 100644 --- a/example/lib/models/address.schema.dart +++ b/example/lib/models/address.schema.dart @@ -97,10 +97,7 @@ class BillingAddressQueryable extends ViewQueryable { @override BillingAddress decode(TypedMap map) => BillingAddressView( - city: map.get('city', TextEncoder.i.decode), - postcode: map.get('postcode', TextEncoder.i.decode), - name: map.get('name', TextEncoder.i.decode), - street: map.get('street', TextEncoder.i.decode)); + city: map.get('city'), postcode: map.get('postcode'), name: map.get('name'), street: map.get('street')); } class BillingAddressView implements BillingAddress { diff --git a/example/lib/models/company.schema.dart b/example/lib/models/company.schema.dart index f457bd2..f744298 100644 --- a/example/lib/models/company.schema.dart +++ b/example/lib/models/company.schema.dart @@ -153,8 +153,8 @@ class FullCompanyViewQueryable extends KeyedViewQueryable MemberCompanyView( - id: map.get('id', TextEncoder.i.decode), - name: map.get('name', TextEncoder.i.decode), + id: map.get('id'), + name: map.get('name'), addresses: map.getListOpt('addresses', BillingAddressQueryable().decoder) ?? const []); } diff --git a/example/lib/models/invoice.schema.dart b/example/lib/models/invoice.schema.dart index 5fbd13a..738a11b 100644 --- a/example/lib/models/invoice.schema.dart +++ b/example/lib/models/invoice.schema.dart @@ -108,10 +108,8 @@ class OwnerInvoiceViewQueryable extends KeyedViewQueryable 'invoices'; @override - OwnerInvoiceView decode(TypedMap map) => OwnerInvoiceView( - id: map.get('id', TextEncoder.i.decode), - title: map.get('title', TextEncoder.i.decode), - invoiceId: map.get('invoice_id', TextEncoder.i.decode)); + OwnerInvoiceView decode(TypedMap map) => + OwnerInvoiceView(id: map.get('id'), title: map.get('title'), invoiceId: map.get('invoice_id')); } class OwnerInvoiceView { diff --git a/example/lib/models/party.schema.dart b/example/lib/models/party.schema.dart index 2316680..4054a7d 100644 --- a/example/lib/models/party.schema.dart +++ b/example/lib/models/party.schema.dart @@ -119,10 +119,10 @@ class GuestPartyViewQueryable extends KeyedViewQueryable @override GuestPartyView decode(TypedMap map) => GuestPartyView( - id: map.get('id', TextEncoder.i.decode), - name: map.get('name', TextEncoder.i.decode), + id: map.get('id'), + name: map.get('name'), sponsor: map.getOpt('sponsor', MemberCompanyViewQueryable().decoder), - date: map.get('date', TextEncoder.i.decode)); + date: map.get('date')); } class GuestPartyView { @@ -154,10 +154,8 @@ class CompanyPartyViewQueryable extends KeyedViewQueryable 'parties'; @override - CompanyPartyView decode(TypedMap map) => CompanyPartyView( - id: map.get('id', TextEncoder.i.decode), - name: map.get('name', TextEncoder.i.decode), - date: map.get('date', TextEncoder.i.decode)); + CompanyPartyView decode(TypedMap map) => + CompanyPartyView(id: map.get('id'), name: map.get('name'), date: map.get('date')); } class CompanyPartyView { diff --git a/lib/src/builder/generators/view_generator.dart b/lib/src/builder/generators/view_generator.dart index dd60066..07c125c 100644 --- a/lib/src/builder/generators/view_generator.dart +++ b/lib/src/builder/generators/view_generator.dart @@ -98,17 +98,17 @@ class ViewGenerator { } var key = column is FieldColumnElement ? column.columnName : c.paramName; - str += "('$key', "; + str += "('$key'"; if (c.view != null) { - str += '${c.view!.entityName}Queryable().decoder)'; + str += ', ${c.view!.entityName}Queryable().decoder)'; } else if (c.column.converter != null) { - str += '${c.column.converter!.toSource()}.decode)'; + str += ', ${c.column.converter!.toSource()}.decode)'; } else if (c.column is FieldColumnElement && (c.column as FieldColumnElement).dataType.isEnum) { var e = (c.column as FieldColumnElement).dataType.element as EnumElement; - str += 'EnumTypeConverter<${e.name}>(${e.name}.values).decode)'; + str += ', EnumTypeConverter<${e.name}>(${e.name}.values).decode)'; } else { - str += 'TextEncoder.i.decode)'; + str += ')'; } if (defVal != null) { diff --git a/lib/src/internals/text_encoder.dart b/lib/src/internals/text_encoder.dart index 0013e34..1108083 100644 --- a/lib/src/internals/text_encoder.dart +++ b/lib/src/internals/text_encoder.dart @@ -116,48 +116,48 @@ class TypedMap { TypedMap(this.map); - T get(String key, Decoder decode) { + T get(String key, [Decoder? decode]) { if (map[key] == null) { throw ConverterException('Parameter $key is required.'); } - return decode(map[key]); + return (decode ?? TextEncoder.i.decode)(map[key]); } - T? getOpt(String key, Decoder decode) { + T? getOpt(String key, [Decoder? decode]) { if (map[key] == null) { return null; } return get(key, decode); } - List getList(String key, Decoder decode) { + List getList(String key, [Decoder? decode]) { if (map[key] == null) { throw ConverterException('Parameter $key is required.'); } else if (map[key] is! List) { throw ConverterException('Parameter $key is not a List'); } List value = map[key] as List; - return value.map((dynamic item) => decode(item)).toList(); + return value.map((dynamic item) => (decode ?? TextEncoder.i.decode)(item)).toList(); } - List? getListOpt(String key, Decoder decode) { + List? getListOpt(String key, [Decoder? decode]) { if (map[key] == null) { return null; } return getList(key, decode); } - Map getMap(String key, Decoder> decode) { + Map getMap(String key, [Decoder>? decode]) { if (map[key] == null) { throw ConverterException('Parameter $key is required.'); } else if (map[key] is! Map) { throw ConverterException('Parameter ${map[key]} with key $key is not a Map'); } Map value = map[key] as Map; - return decode(value); + return (decode ?? TextEncoder.i.decode)(value); } - Map? getMapOpt(String key, Decoder> decode) { + Map? getMapOpt(String key, [Decoder>? decode]) { if (map[key] == null) { return null; } From 41bdb2f145631d23b2b1ce0b318573729b71eed3 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Sun, 15 Jan 2023 10:07:43 +0100 Subject: [PATCH 7/9] sort model columns --- example/lib/model.schema.dart | 24 +++---- example/lib/models/account.dart | 2 +- example/lib/models/account.schema.dart | 70 ++++++++++----------- example/lib/models/address.schema.dart | 24 +++---- example/lib/models/company.schema.dart | 56 ++++++++--------- lib/src/builder/elements/table_element.dart | 34 ++++++++-- lib/src/builder/schema.dart | 3 + 7 files changed, 119 insertions(+), 94 deletions(-) diff --git a/example/lib/model.schema.dart b/example/lib/model.schema.dart index a092926..5a35bff 100644 --- a/example/lib/model.schema.dart +++ b/example/lib/model.schema.dart @@ -92,8 +92,8 @@ class _BRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "bs" ( "a_id", "id" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(r.aId)}, ${values.add(r.id)} )').join(', ')}\n', + 'INSERT INTO "bs" ( "id", "a_id" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.aId)} )').join(', ')}\n', values.values, ); } @@ -105,8 +105,8 @@ class _BRepository extends BaseRepository await db.query( 'UPDATE "bs"\n' 'SET "a_id" = COALESCE(UPDATED."a_id"::text, "bs"."a_id")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.aId)}, ${values.add(r.id)} )').join(', ')} )\n' - 'AS UPDATED("a_id", "id")\n' + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.id)}, ${values.add(r.aId)} )').join(', ')} )\n' + 'AS UPDATED("id", "a_id")\n' 'WHERE "bs"."id" = UPDATED."id"', values.values, ); @@ -125,12 +125,12 @@ class AInsertRequest { class BInsertRequest { BInsertRequest({ - this.aId, required this.id, + this.aId, }); - String? aId; String id; + String? aId; } class AUpdateRequest { @@ -145,12 +145,12 @@ class AUpdateRequest { class BUpdateRequest { BUpdateRequest({ - this.aId, required this.id, + this.aId, }); - String? aId; String id; + String? aId; } class AQueryable extends KeyedViewQueryable { @@ -202,17 +202,17 @@ class BQueryable extends KeyedViewQueryable { String get tableAlias => 'bs'; @override - B decode(TypedMap map) => BView(a: map.getOpt('a', AQueryable().decoder), id: map.get('id')); + B decode(TypedMap map) => BView(id: map.get('id'), a: map.getOpt('a', AQueryable().decoder)); } class BView implements B { BView({ - this.a, required this.id, + this.a, }); - @override - final A? a; @override final String id; + @override + final A? a; } diff --git a/example/lib/models/account.dart b/example/lib/models/account.dart index 2ac96a1..df54253 100644 --- a/example/lib/models/account.dart +++ b/example/lib/models/account.dart @@ -7,7 +7,7 @@ import 'latlng.dart'; import 'party.dart'; part 'account.schema.dart'; - +//a @Model(views: [#Full, #User, #Company]) abstract class Account { @PrimaryKey() diff --git a/example/lib/models/account.schema.dart b/example/lib/models/account.schema.dart index 9b491e8..3612963 100644 --- a/example/lib/models/account.schema.dart +++ b/example/lib/models/account.schema.dart @@ -72,12 +72,12 @@ class _AccountRepository extends BaseRepository ); await db.billingAddresses.insertMany(requests.where((r) => r.billingAddress != null).map((r) { return BillingAddressInsertRequest( - accountId: TextEncoder.i.decode(autoIncrements[requests.indexOf(r)]['id']), - companyId: null, city: r.billingAddress!.city, postcode: r.billingAddress!.postcode, name: r.billingAddress!.name, - street: r.billingAddress!.street); + street: r.billingAddress!.street, + accountId: TextEncoder.i.decode(autoIncrements[requests.indexOf(r)]['id']), + companyId: null); }).toList()); return autoIncrements.map((m) => TextEncoder.i.decode(m['id'])).toList(); @@ -97,11 +97,11 @@ class _AccountRepository extends BaseRepository ); await db.billingAddresses.updateMany(requests.where((r) => r.billingAddress != null).map((r) { return BillingAddressUpdateRequest( - accountId: r.id, city: r.billingAddress!.city, postcode: r.billingAddress!.postcode, name: r.billingAddress!.name, - street: r.billingAddress!.street); + street: r.billingAddress!.street, + accountId: r.id); }).toList()); } } @@ -149,8 +149,10 @@ class FullAccountViewQueryable extends KeyedViewQueryable @override String get query => - 'SELECT "accounts".*, "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress", row_to_json("company".*) as "company"' + 'SELECT "accounts".*, row_to_json("billingAddress".*) as "billingAddress", "invoices"."data" as "invoices", row_to_json("company".*) as "company", "parties"."data" as "parties"' 'FROM "accounts"' + 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' + 'ON "accounts"."id" = "billingAddress"."account_id"' 'LEFT JOIN (' ' SELECT "invoices"."account_id",' ' to_jsonb(array_agg("invoices".*)) as data' @@ -158,6 +160,8 @@ class FullAccountViewQueryable extends KeyedViewQueryable ' GROUP BY "invoices"."account_id"' ') "invoices"' 'ON "accounts"."id" = "invoices"."account_id"' + 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' + 'ON "accounts"."company_id" = "company"."id"' 'LEFT JOIN (' ' SELECT "accounts_parties"."account_id",' ' to_jsonb(array_agg("parties".*)) as data' @@ -166,47 +170,43 @@ class FullAccountViewQueryable extends KeyedViewQueryable ' ON "parties"."id" = "accounts_parties"."party_id"' ' GROUP BY "accounts_parties"."account_id"' ') "parties"' - 'ON "accounts"."id" = "parties"."account_id"' - 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' - 'ON "accounts"."id" = "billingAddress"."account_id"' - 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' - 'ON "accounts"."company_id" = "company"."id"'; + 'ON "accounts"."id" = "parties"."account_id"'; @override String get tableAlias => 'accounts'; @override FullAccountView decode(TypedMap map) => FullAccountView( - invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], - parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], id: map.get('id'), firstName: map.get('first_name'), lastName: map.get('last_name'), location: map.get('location', LatLngConverter().decode), billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), - company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); + invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], + company: map.getOpt('company', MemberCompanyViewQueryable().decoder), + parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const []); } class FullAccountView { FullAccountView({ - required this.invoices, - required this.parties, required this.id, required this.firstName, required this.lastName, required this.location, this.billingAddress, + required this.invoices, this.company, + required this.parties, }); - final List invoices; - final List parties; final int id; final String firstName; final String lastName; final LatLng location; final BillingAddress? billingAddress; + final List invoices; final MemberCompanyView? company; + final List parties; } class UserAccountViewQueryable extends KeyedViewQueryable { @@ -218,8 +218,10 @@ class UserAccountViewQueryable extends KeyedViewQueryable @override String get query => - 'SELECT "accounts".*, "invoices"."data" as "invoices", "parties"."data" as "parties", row_to_json("billingAddress".*) as "billingAddress", row_to_json("company".*) as "company"' + 'SELECT "accounts".*, row_to_json("billingAddress".*) as "billingAddress", "invoices"."data" as "invoices", row_to_json("company".*) as "company", "parties"."data" as "parties"' 'FROM "accounts"' + 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' + 'ON "accounts"."id" = "billingAddress"."account_id"' 'LEFT JOIN (' ' SELECT "invoices"."account_id",' ' to_jsonb(array_agg("invoices".*)) as data' @@ -227,6 +229,8 @@ class UserAccountViewQueryable extends KeyedViewQueryable ' GROUP BY "invoices"."account_id"' ') "invoices"' 'ON "accounts"."id" = "invoices"."account_id"' + 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' + 'ON "accounts"."company_id" = "company"."id"' 'LEFT JOIN (' ' SELECT "accounts_parties"."account_id",' ' to_jsonb(array_agg("parties".*)) as data' @@ -235,47 +239,43 @@ class UserAccountViewQueryable extends KeyedViewQueryable ' ON "parties"."id" = "accounts_parties"."party_id"' ' GROUP BY "accounts_parties"."account_id"' ') "parties"' - 'ON "accounts"."id" = "parties"."account_id"' - 'LEFT JOIN (${BillingAddressQueryable().query}) "billingAddress"' - 'ON "accounts"."id" = "billingAddress"."account_id"' - 'LEFT JOIN (${MemberCompanyViewQueryable().query}) "company"' - 'ON "accounts"."company_id" = "company"."id"'; + 'ON "accounts"."id" = "parties"."account_id"'; @override String get tableAlias => 'accounts'; @override UserAccountView decode(TypedMap map) => UserAccountView( - invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], - parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const [], id: map.get('id'), firstName: map.get('first_name'), lastName: map.get('last_name'), location: map.get('location', LatLngConverter().decode), billingAddress: map.getOpt('billingAddress', BillingAddressQueryable().decoder), - company: map.getOpt('company', MemberCompanyViewQueryable().decoder)); + invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], + company: map.getOpt('company', MemberCompanyViewQueryable().decoder), + parties: map.getListOpt('parties', GuestPartyViewQueryable().decoder) ?? const []); } class UserAccountView { UserAccountView({ - required this.invoices, - required this.parties, required this.id, required this.firstName, required this.lastName, required this.location, this.billingAddress, + required this.invoices, this.company, + required this.parties, }); - final List invoices; - final List parties; final int id; final String firstName; final String lastName; final LatLng location; final BillingAddress? billingAddress; + final List invoices; final MemberCompanyView? company; + final List parties; } class CompanyAccountViewQueryable extends KeyedViewQueryable { @@ -304,25 +304,25 @@ class CompanyAccountViewQueryable extends KeyedViewQueryable CompanyAccountView( - parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const [], id: map.get('id'), firstName: map.get('first_name'), lastName: map.get('last_name'), - location: map.get('location', LatLngConverter().decode)); + location: map.get('location', LatLngConverter().decode), + parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const []); } class CompanyAccountView { CompanyAccountView({ - required this.parties, required this.id, required this.firstName, required this.lastName, required this.location, + required this.parties, }); - final List parties; final int id; final String firstName; final String lastName; final LatLng location; + final List parties; } diff --git a/example/lib/models/address.schema.dart b/example/lib/models/address.schema.dart index fa17cba..ec0caad 100644 --- a/example/lib/models/address.schema.dart +++ b/example/lib/models/address.schema.dart @@ -30,8 +30,8 @@ class _BillingAddressRepository extends BaseRepository var values = QueryValues(); await db.query( - 'INSERT INTO "billing_addresses" ( "account_id", "company_id", "city", "postcode", "name", "street" )\n' - 'VALUES ${requests.map((r) => '( ${values.add(r.accountId)}, ${values.add(r.companyId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')}\n', + 'INSERT INTO "billing_addresses" ( "city", "postcode", "name", "street", "account_id", "company_id" )\n' + 'VALUES ${requests.map((r) => '( ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)}, ${values.add(r.accountId)}, ${values.add(r.companyId)} )').join(', ')}\n', values.values, ); } @@ -43,8 +43,8 @@ class _BillingAddressRepository extends BaseRepository await db.query( 'UPDATE "billing_addresses"\n' 'SET "city" = COALESCE(UPDATED."city"::text, "billing_addresses"."city"), "postcode" = COALESCE(UPDATED."postcode"::text, "billing_addresses"."postcode"), "name" = COALESCE(UPDATED."name"::text, "billing_addresses"."name"), "street" = COALESCE(UPDATED."street"::text, "billing_addresses"."street")\n' - 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.accountId)}, ${values.add(r.companyId)}, ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)} )').join(', ')} )\n' - 'AS UPDATED("account_id", "company_id", "city", "postcode", "name", "street")\n' + 'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.city)}, ${values.add(r.postcode)}, ${values.add(r.name)}, ${values.add(r.street)}, ${values.add(r.accountId)}, ${values.add(r.companyId)} )').join(', ')} )\n' + 'AS UPDATED("city", "postcode", "name", "street", "account_id", "company_id")\n' 'WHERE "billing_addresses"."account_id" = UPDATED."account_id" AND "billing_addresses"."company_id" = UPDATED."company_id"', values.values, ); @@ -53,38 +53,38 @@ class _BillingAddressRepository extends BaseRepository class BillingAddressInsertRequest { BillingAddressInsertRequest({ - this.accountId, - this.companyId, required this.city, required this.postcode, required this.name, required this.street, + this.accountId, + this.companyId, }); - int? accountId; - String? companyId; String city; String postcode; String name; String street; + int? accountId; + String? companyId; } class BillingAddressUpdateRequest { BillingAddressUpdateRequest({ - this.accountId, - this.companyId, this.city, this.postcode, this.name, this.street, + this.accountId, + this.companyId, }); - int? accountId; - String? companyId; String? city; String? postcode; String? name; String? street; + int? accountId; + String? companyId; } class BillingAddressQueryable extends ViewQueryable { diff --git a/example/lib/models/company.schema.dart b/example/lib/models/company.schema.dart index f744298..8c11ec6 100644 --- a/example/lib/models/company.schema.dart +++ b/example/lib/models/company.schema.dart @@ -58,7 +58,7 @@ class _CompanyRepository extends BaseRepository ); await db.billingAddresses.insertMany(requests.expand((r) { return r.addresses.map((rr) => BillingAddressInsertRequest( - accountId: null, companyId: r.id, city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street)); + city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street, accountId: null, companyId: r.id)); }).toList()); } @@ -76,7 +76,7 @@ class _CompanyRepository extends BaseRepository ); await db.billingAddresses.updateMany(requests.where((r) => r.addresses != null).expand((r) { return r.addresses!.map((rr) => BillingAddressUpdateRequest( - companyId: r.id, city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street)); + city: rr.city, postcode: rr.postcode, name: rr.name, street: rr.street, companyId: r.id)); }).toList()); } } @@ -114,9 +114,23 @@ class FullCompanyViewQueryable extends KeyedViewQueryable - 'SELECT "companies".*, "invoices"."data" as "invoices", "parties"."data" as "parties", "members"."data" as "members", "addresses"."data" as "addresses"' + 'SELECT "companies".*, "addresses"."data" as "addresses", "members"."data" as "members", "invoices"."data" as "invoices", "parties"."data" as "parties"' 'FROM "companies"' 'LEFT JOIN (' + ' SELECT "billing_addresses"."company_id",' + ' to_jsonb(array_agg("billing_addresses".*)) as data' + ' FROM (${BillingAddressQueryable().query}) "billing_addresses"' + ' GROUP BY "billing_addresses"."company_id"' + ') "addresses"' + 'ON "companies"."id" = "addresses"."company_id"' + 'LEFT JOIN (' + ' SELECT "accounts"."company_id",' + ' to_jsonb(array_agg("accounts".*)) as data' + ' FROM (${CompanyAccountViewQueryable().query}) "accounts"' + ' GROUP BY "accounts"."company_id"' + ') "members"' + 'ON "companies"."id" = "members"."company_id"' + 'LEFT JOIN (' ' SELECT "invoices"."company_id",' ' to_jsonb(array_agg("invoices".*)) as data' ' FROM (${OwnerInvoiceViewQueryable().query}) "invoices"' @@ -129,51 +143,37 @@ class FullCompanyViewQueryable extends KeyedViewQueryable 'companies'; @override FullCompanyView decode(TypedMap map) => FullCompanyView( - invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], - parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const [], - members: map.getListOpt('members', CompanyAccountViewQueryable().decoder) ?? const [], id: map.get('id'), name: map.get('name'), - addresses: map.getListOpt('addresses', BillingAddressQueryable().decoder) ?? const []); + addresses: map.getListOpt('addresses', BillingAddressQueryable().decoder) ?? const [], + members: map.getListOpt('members', CompanyAccountViewQueryable().decoder) ?? const [], + invoices: map.getListOpt('invoices', OwnerInvoiceViewQueryable().decoder) ?? const [], + parties: map.getListOpt('parties', CompanyPartyViewQueryable().decoder) ?? const []); } class FullCompanyView { FullCompanyView({ - required this.invoices, - required this.parties, - required this.members, required this.id, required this.name, required this.addresses, + required this.members, + required this.invoices, + required this.parties, }); - final List invoices; - final List parties; - final List members; final String id; final String name; final List addresses; + final List members; + final List invoices; + final List parties; } class MemberCompanyViewQueryable extends KeyedViewQueryable { diff --git a/lib/src/builder/elements/table_element.dart b/lib/src/builder/elements/table_element.dart index c4464e8..2de5eef 100644 --- a/lib/src/builder/elements/table_element.dart +++ b/lib/src/builder/elements/table_element.dart @@ -88,10 +88,11 @@ class TableElement { ? columns.whereType().where((c) => c.parameter == primaryKeyParameter).firstOrNull : null; - void prepareColumns() { - final allFields = - element.fields.followedBy(element.allSupertypes.expand((t) => t.isDartCoreObject ? [] : t.element.fields)); + late List allFields = element.fields + .followedBy(element.allSupertypes.expand((t) => t.isDartCoreObject ? [] : t.element.fields)) + .toList(); + void prepareColumns() { for (var param in allFields) { if (columns.any((c) => c.parameter == param)) { continue; @@ -124,7 +125,8 @@ class TableElement { } if (selfHasKey && otherHasKey && !selfIsList && !otherIsList) { - var eitherNullable = param.type.nullabilitySuffix != NullabilitySuffix.none || otherParam!.type.nullabilitySuffix != NullabilitySuffix.none; + var eitherNullable = param.type.nullabilitySuffix != NullabilitySuffix.none || + otherParam!.type.nullabilitySuffix != NullabilitySuffix.none; if (!eitherNullable) { throw 'Model ${otherBuilder.element.name} cannot have a one-to-one relation to model ${element.name} with ' 'both sides being non-nullable. At least one side has to be nullable, to insert one model before the other.\n' @@ -169,8 +171,7 @@ class TableElement { if (selfHasKey && !otherIsList) { otherColumn = ForeignColumnElement(otherParam, this, otherBuilder, state); - var insertIndex = otherBuilder.columns.lastIndexWhere((c) => c is ForeignColumnElement) + 1; - otherBuilder.columns.insert(insertIndex, otherColumn); + otherBuilder.columns.add(otherColumn); } else { otherColumn = ReferenceColumnElement(otherParam, this, otherBuilder, state); otherBuilder.columns.add(otherColumn); @@ -194,6 +195,27 @@ class TableElement { } } + void sortColumns() { + columns.sortBy((column) { + var key = ''; + + if (column.parameter != null) { + // first: columns related to a model field, in declared order + key += '0_'; + key += allFields.indexOf(column.parameter!).toString(); + } else if (column is ParameterColumnElement) { + // then: foreign or reference columns with no field, in alphabetical order + key += '1_'; + key += column.paramName; + } else { + // then: rest + key += '2'; + } + + return key; + }); + } + FieldElement? findMatchingParam(FieldElement param) { // TODO add binding return element.fields.where((p) { diff --git a/lib/src/builder/schema.dart b/lib/src/builder/schema.dart index 995d88b..a4f0613 100644 --- a/lib/src/builder/schema.dart +++ b/lib/src/builder/schema.dart @@ -31,6 +31,9 @@ class SchemaState { for (var element in tables.values) { element.prepareColumns(); } + for (var element in tables.values) { + element.sortColumns(); + } _didFinalize = true; } } From 5314f61af51b002c24725e0eed9621bf09cd3623 Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Sun, 15 Jan 2023 12:10:16 +0100 Subject: [PATCH 8/9] Simplify constructor parameter generation --- lib/src/builder/generators/insert_generator.dart | 5 +++-- lib/src/builder/generators/update_generator.dart | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/builder/generators/insert_generator.dart b/lib/src/builder/generators/insert_generator.dart index 35cb253..6001a38 100644 --- a/lib/src/builder/generators/insert_generator.dart +++ b/lib/src/builder/generators/insert_generator.dart @@ -148,11 +148,12 @@ class InsertGenerator { } var constructorParameters = - requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', '); + requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value},').join(' '); + return ''' ${table.annotateWith ?? ''} class $requestClassName { - $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters,}' : ''}); + $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters}' : ''}); ${requestFields.map((f) => '${f.key} ${f.value};').join('\n')} } diff --git a/lib/src/builder/generators/update_generator.dart b/lib/src/builder/generators/update_generator.dart index 92f772b..786bc57 100644 --- a/lib/src/builder/generators/update_generator.dart +++ b/lib/src/builder/generators/update_generator.dart @@ -144,12 +144,12 @@ class UpdateGenerator { } final constructorParameters = - requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value}').join(', '); + requestFields.map((f) => '${f.key.endsWith('?') ? '' : 'required '}this.${f.value},').join(' '); return ''' ${table.annotateWith ?? ''} class $requestClassName { - $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters,}' : ''}); + $requestClassName(${constructorParameters.isNotEmpty ? '{$constructorParameters}' : ''}); ${requestFields.map((f) => '${f.key} ${f.value};').join('\n')} } From e3f59d95b87484cc6aea43ae0cca258f2ab82f31 Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Sun, 15 Jan 2023 12:15:30 +0100 Subject: [PATCH 9/9] Remove recase dependency --- lib/src/builder/generators/repository_generator.dart | 4 ++-- pubspec.yaml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/builder/generators/repository_generator.dart b/lib/src/builder/generators/repository_generator.dart index 00fbd1a..15d16d7 100644 --- a/lib/src/builder/generators/repository_generator.dart +++ b/lib/src/builder/generators/repository_generator.dart @@ -1,6 +1,6 @@ -import 'package:recase/recase.dart'; import 'package:path/path.dart' as p; +import '../../core/case_style.dart'; import '../schema.dart'; import '../elements/table_element.dart'; import 'insert_generator.dart'; @@ -10,7 +10,7 @@ import 'view_generator.dart'; class RepositoryGenerator { String generateRepositories(AssetState state) { return ''' - extension ${p.withoutExtension(state.filename).pascalCase}Repositories on Database { + extension ${CaseStyle.pascalCase.transform(p.withoutExtension(state.filename))}Repositories on Database { ${state.tables.values.map((b) => ' ${b.element.name}Repository get ${b.repoName} => ${b.element.name}Repository._(this);\n').join()} } diff --git a/pubspec.yaml b/pubspec.yaml index 6f8cdba..179eb86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,6 @@ dependencies: dart_style: ^2.2.4 path: ^1.8.2 postgres: ^2.5.1 - recase: ^4.1.0 source_gen: ^1.2.3 yaml: ^3.1.1