From b1fcc4b7a00edd06a00ffba7eea975ae766a7de9 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Tue, 15 Jun 2021 12:32:55 +0200 Subject: [PATCH] QmlDesigner: Relink alias properties There are still some corner cases but this will be done in follow-up patches. The link information moved to the typenames table so only indirections are saved in the propertyDeclarations table. Otherwise the duplication would increase the database size too much. Task-number: QDS-4551 Change-Id: I4aca85dd2d803b43aa9860183e500ced2d91141f Reviewed-by: Vikas Pachdha Reviewed-by: Qt CI Bot --- .../designercore/include/projectstorageids.h | 6 +- .../projectstorage/projectstorage.h | 581 +++++++++++++++--- .../projectstorage/projectstoragetypes.h | 30 +- tests/unit/unittest/projectstorage-test.cpp | 149 ++++- 4 files changed, 650 insertions(+), 116 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h index 140ed6f234e..5b25f128d2e 100644 --- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -83,7 +83,8 @@ enum class BasicIdType { FunctionDeclaration, SignalDeclaration, EnumerationDeclaration, - Import + Import, + TypeName }; using TypeId = BasicId; @@ -110,4 +111,7 @@ using SourceIds = std::vector; using ImportId = BasicId; using ImportIds = std::vector; +using TypeNameId = BasicId; +using TypeNameIds = std::vector; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index fb2e7941c0b..97cd2faf695 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -70,6 +70,9 @@ public: updatedTypeIds.push_back(declareType(type)); } + for (auto &&type : types) + syncPrototypes(type); + for (auto &&type : types) synchronizeAliasPropertyDeclarationsRemoval(type); @@ -94,6 +97,16 @@ public: transaction.commit(); } + void synchronizeDocuments(Storage::Documents documents) + { + Sqlite::ImmediateTransaction transaction{database}; + + for (auto &&document : documents) + synchronizeDocumentImports(document.sourceId, document.importIds); + + transaction.commit(); + } + ImportIds fetchImportIds(const Storage::Imports &imports) { ImportIds importIds; @@ -108,6 +121,11 @@ public: return importIds; } + ImportIds fetchImportIds(SourceId sourceId) + { + return selectImportIdsForSourceIdStatement.template valeWithTransaction(16, &sourceId); + } + ImportIds fetchImportDependencyIds(ImportIds importIds) const { return fetchImportDependencyIdsStatement.template valuesWithTransaction( @@ -417,9 +435,108 @@ private: return selectImportIdByNameStatement.template value(import.name); } - void deleteType(TypeId typeId) + class AliasPropertyDeclaration { - deleteExportTypesByTypeIdStatement.write(&typeId); + public: + explicit AliasPropertyDeclaration(PropertyDeclarationId propertyDeclarationId, + Storage::TypeName aliasTypeName, + Utils::SmallString &&aliasPropertyName, + SourceId sourceId) + : propertyDeclarationId{propertyDeclarationId} + , aliasTypeName{std::move(aliasTypeName)} + , aliasPropertyName{std::move(aliasPropertyName)} + , sourceId{sourceId} + {} + + public: + PropertyDeclarationId propertyDeclarationId; + Storage::TypeName aliasTypeName; + Utils::SmallString aliasPropertyName; + SourceId sourceId; + }; + + class PropertyDeclaration + { + public: + explicit PropertyDeclaration(PropertyDeclarationId propertyDeclarationId, + Storage::TypeName typeName, + SourceId sourceId) + : propertyDeclarationId{propertyDeclarationId} + , typeName{std::move(typeName)} + , sourceId{sourceId} + {} + + public: + PropertyDeclarationId propertyDeclarationId; + Storage::TypeName typeName; + SourceId sourceId; + }; + + Storage::TypeName fetchTypeName(TypeNameId typeNameId) + { + Storage::TypeName typeName; + + auto callback = [&](Utils::SmallStringView name, long long kind) { + typeName = createTypeName(kind, name); + return Sqlite::CallbackControl::Abort; + }; + + selectTypeNameStatement.readCallback(callback, &typeNameId); + + return typeName; + } + + void handleAliasPropertyDeclarationsWithPropertyType( + TypeId typeId, std::vector &relinkableAliasPropertyDeclarations) + { + auto callback = [&](long long propertyDeclarationId, + long long propertyTypeNameId, + long long typeIdFromProperty, + long long aliasPropertyDeclarationId) { + auto sourceId = selectSourceIdForTypeIdStatement.template value( + typeIdFromProperty); + + auto aliasPropertyName = selectPropertyNameStatement.template value( + aliasPropertyDeclarationId); + + relinkableAliasPropertyDeclarations + .emplace_back(PropertyDeclarationId{propertyDeclarationId}, + fetchTypeName(TypeNameId{propertyTypeNameId}), + std::move(aliasPropertyName), + sourceId); + + updateAliasPropertyDeclarationToNullStatement.write(propertyDeclarationId); + + return Sqlite::CallbackControl::Continue; + }; + + selectAliasPropertiesDeclarationForPropertiesWithTypeIdStatement.readCallback(callback, + &typeId); + } + + void handlePropertyDeclarationWithPropertyType( + TypeId typeId, std::vector &relinkablePropertyDeclarations) + { + auto callback = [&](long long propertyDeclarationId, long long propertyTypeNameId) { + auto sourceId = selectSourceIdForTypeIdStatement.template value(&typeId); + + relinkablePropertyDeclarations.emplace_back(PropertyDeclarationId{propertyDeclarationId}, + fetchTypeName(TypeNameId{propertyTypeNameId}), + sourceId); + + return Sqlite::CallbackControl::Continue; + }; + + updatesPropertyDeclarationPropertyTypeToNullStatement.readCallback(callback, &typeId); + } + + void deleteType(TypeId typeId, + std::vector &relinkableAliasPropertyDeclarations, + std::vector &relinkablePropertyDeclarations) + { + handlePropertyDeclarationWithPropertyType(typeId, relinkablePropertyDeclarations); + handleAliasPropertyDeclarationsWithPropertyType(typeId, relinkableAliasPropertyDeclarations); + deleteTypeNamesByTypeIdStatement.write(&typeId); deleteEnumerationDeclarationByTypeIdStatement.write(&typeId); deletePropertyDeclarationByTypeIdStatement.write(&typeId); deleteFunctionDeclarationByTypeIdStatement.write(&typeId); @@ -427,8 +544,50 @@ private: deleteTypeStatement.write(&typeId); } + void relinkAliasPropertyDeclaration(const std::vector &aliasPropertyDeclarations) + { + for (const AliasPropertyDeclaration &alias : aliasPropertyDeclarations) { + auto [typeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(alias.aliasTypeName, + alias.sourceId); + + auto [propertyTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeIdAndNameUngarded( + typeId, alias.aliasPropertyName); + + updatePropertyDeclarationWithAliasStatement.write(&alias.propertyDeclarationId, + &propertyTypeId, + propertyTraits, + &aliasTypeNameId, + &aliasId); + } + } + + void relinkPropertyDeclaration(const std::vector &relinkablePropertyDeclaration) + { + for (const PropertyDeclaration &property : relinkablePropertyDeclaration) { + auto [propertyTypeId, propertyTypeNameId] = fetchTypeIdByNameUngarded(property.typeName, + property.sourceId); + + if (!propertyTypeId) { + auto hasPropertyDeclaration = selectPropertyDeclarationIdStatement + .template optionalValue( + &property.propertyDeclarationId); + if (hasPropertyDeclaration) + throw TypeNameDoesNotExists{}; + + continue; + } + + updatePropertyDeclarationTypeStatement.write(&property.propertyDeclarationId, + &propertyTypeId, + &propertyTypeNameId); + } + } + void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &sourceIds) { + std::vector relinkableAliasPropertyDeclarations; + std::vector relinkablePropertyDeclarations; + auto updatedTypeIdValues = Utils::transform(updatedTypeIds, [](TypeId typeId) { return &typeId; }); @@ -438,19 +597,27 @@ private: }); auto callback = [&](long long typeId) { - deleteType(TypeId{typeId}); + deleteType(TypeId{typeId}, + relinkableAliasPropertyDeclarations, + relinkablePropertyDeclarations); return Sqlite::CallbackControl::Continue; }; selectNotUpdatedTypesInSourcesStatement.readCallback(callback, Utils::span(sourceIdValues), Utils::span(updatedTypeIdValues)); + + relinkPropertyDeclaration(relinkablePropertyDeclarations); + relinkAliasPropertyDeclaration(relinkableAliasPropertyDeclarations); } void deleteTypesForImportId(ImportId importId) { + std::vector aliasPropertyDeclarations; + std::vector relinkablePropertyDeclarations; + auto callback = [&](long long typeId) { - deleteType(TypeId{typeId}); + deleteType(TypeId{typeId}, aliasPropertyDeclarations, relinkablePropertyDeclarations); return Sqlite::CallbackControl::Continue; }; @@ -459,12 +626,23 @@ private: void upsertExportedType(ImportId importId, Utils::SmallStringView name, TypeId typeId) { - upsertExportedTypesStatement.write(&importId, name, &typeId); + upsertTypeNamesStatement.write(&importId, + name, + &typeId, + static_cast(Storage::TypeNameKind::Exported)); + } + + void upsertNativeType(ImportId importId, Utils::SmallStringView name, TypeId typeId) + { + upsertTypeNamesStatement.write(&importId, + name, + &typeId, + static_cast(Storage::TypeNameKind::Native)); } void synchronizePropertyDeclarations(TypeId typeId, Storage::PropertyDeclarations &propertyDeclarations, - ImportIds &importIds) + SourceId sourceId) { std::sort(propertyDeclarations.begin(), propertyDeclarations.end(), @@ -481,27 +659,60 @@ private: }; auto insert = [&](const Storage::PropertyDeclaration &value) { - auto propertyTypeId = fetchTypeIdByNameUngarded(value.typeName, importIds); + auto [propertyTypeId, propertyTypeNameId] = fetchTypeIdByNameUngarded(value.typeName, + sourceId); - insertPropertyDeclarationStatement.write(&typeId, - value.name, - &propertyTypeId, - static_cast(value.traits)); + if (!propertyTypeId) + throw TypeNameDoesNotExists{}; + + auto propertyDeclarationId = insertPropertyDeclarationStatement.template value< + PropertyDeclarationId>(&typeId, + value.name, + &propertyTypeId, + static_cast(value.traits), + &propertyTypeNameId); + + auto nextPropertyDeclarationId = selectPropertyDeclarationIdPrototypeChainDownStatement + .template value(&typeId, + value.name); + if (nextPropertyDeclarationId) { + updateAliasPropertyDeclarationStatement.write(&nextPropertyDeclarationId, + &propertyTypeId, + static_cast(value.traits), + &propertyDeclarationId); + } }; auto update = [&](const Storage::PropertyDeclarationView &view, const Storage::PropertyDeclaration &value) { - auto propertyTypeId = fetchTypeIdByNameUngarded(value.typeName, importIds); + auto [propertyTypeId, propertyTypeNameId] = fetchTypeIdByNameUngarded(value.typeName, + sourceId); - if (view.traits == value.traits && propertyTypeId == view.typeId) + if (!propertyTypeId) + throw TypeNameDoesNotExists{}; + + if (view.traits == value.traits && propertyTypeId == view.typeId + && propertyTypeNameId == view.typeNameId) return; updatePropertyDeclarationStatement.write(&view.id, &propertyTypeId, - static_cast(value.traits)); + static_cast(value.traits), + &propertyTypeNameId); + updatePropertyDeclarationWithAliasIdStatement.write(&view.id, + &propertyTypeId, + static_cast(value.traits)); }; auto remove = [&](const Storage::PropertyDeclarationView &view) { + auto nextPropertyDeclarationId = selectPropertyDeclarationIdPrototypeChainDownStatement + .template value(&typeId, + view.name); + if (nextPropertyDeclarationId) { + updateAliasPropertyDeclarationByAliasPropertyDeclarationIdStatement + .write(&nextPropertyDeclarationId, &view.id); + } + deletePropertyDeclarationStatement.write(&view.id); }; @@ -541,7 +752,6 @@ private: { auto &aliasDeclarations = type.aliasDeclarations; TypeId typeId = type.typeId; - ImportIds &importIds = type.importIds; std::sort(aliasDeclarations.begin(), aliasDeclarations.end(), [](auto &&first, auto &&second) { return Sqlite::compare(first.name, second.name) < 0; @@ -556,20 +766,31 @@ private: }; auto insert = [&](const Storage::AliasPropertyDeclaration &value) { - auto [aliasTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeNameAndName( - value.aliasTypeName, value.aliasPropertyName, importIds); + auto [propertyTypeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(value.aliasTypeName, + type.sourceId); - insertPropertyDeclarationWithAliasStatement.write(&typeId, - value.name, - &aliasTypeId, - propertyTraits, - &aliasId); + if (!propertyTypeId) + throw TypeNameDoesNotExists{}; + + auto [aliasTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeIdAndNameUngarded( + propertyTypeId, value.aliasPropertyName); + + Utils::SmallStringView aliasTypeName = extractTypeName(value.aliasTypeName); + + insertPropertyDeclarationWithAliasStatement.write( + &typeId, value.name, &aliasTypeId, propertyTraits, &aliasTypeNameId, &aliasId); }; auto update = [&](const Storage::AliasPropertyDeclarationView &view, const Storage::AliasPropertyDeclaration &value) { - auto [aliasTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeNameAndName( - value.aliasTypeName, value.aliasPropertyName, importIds); + auto [propertyTypeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(value.aliasTypeName, + type.sourceId); + + if (!propertyTypeId) + throw TypeNameDoesNotExists{}; + + auto [aliasTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeIdAndNameUngarded( + propertyTypeId, value.aliasPropertyName); if (view.aliasId == aliasId) return; @@ -577,6 +798,7 @@ private: updatePropertyDeclarationWithAliasStatement.write(&view.id, &aliasTypeId, propertyTraits, + &aliasTypeNameId, &aliasId); }; @@ -585,6 +807,27 @@ private: Sqlite::insertUpdateDelete(range, aliasDeclarations, compareKey, insert, update, remove); } + void synchronizeDocumentImports(SourceId sourceId, ImportIds &importIds) + { + std::sort(importIds.begin(), importIds.end()); + + auto range = selectImportIdsForSourceIdStatement.template range(&sourceId); + + auto compareKey = [](ImportId first, ImportId second) { return first.id - second.id; }; + + auto insert = [&](ImportId importId) { + insertImportIdForSourceIdStatement.write(&sourceId, &importId); + }; + + auto update = [](ImportId, ImportId) {}; + + auto remove = [&](ImportId importId) { + deleteImportIdForSourceIdStatement.write(&sourceId, &importId); + }; + + Sqlite::insertUpdateDelete(range, importIds, compareKey, insert, update, remove); + } + Utils::PathString createJson(const Storage::ParameterDeclarations ¶meters) { Utils::PathString json; @@ -762,6 +1005,8 @@ private: static_cast(type.accessSemantics), &type.sourceId); + upsertNativeType(type.importId, type.typeName, type.typeId); + for (const auto &exportedType : type.exportedTypes) upsertExportedType(type.importId, exportedType.name, type.typeId); @@ -772,69 +1017,114 @@ private: { auto typeId = type.typeId; - auto prototypeId = fetchTypeIdByNameUngarded(type.prototype, type.importIds); - - updatePrototypeStatement.write(&typeId, &prototypeId); - - synchronizePropertyDeclarations(typeId, type.propertyDeclarations, type.importIds); + synchronizePropertyDeclarations(typeId, type.propertyDeclarations, type.sourceId); synchronizeFunctionDeclarations(typeId, type.functionDeclarations); synchronizeSignalDeclarations(typeId, type.signalDeclarations); synchronizeEnumerationDeclarations(typeId, type.enumerationDeclarations); } - TypeId fetchTypeIdByNameUngarded(const Storage::TypeName &name, ImportIds &importIds) + void syncPrototypes(Storage::Type &type) { - if (Utils::visit([](auto &&type) -> bool { return type.name.isEmpty(); }, name)) - return TypeId{}; + if (Utils::visit([](auto &&type) -> bool { return type.name.isEmpty(); }, type.prototype)) { + updatePrototypeStatement.write(&type.typeId, Sqlite::NullValue{}); + } else { + auto [prototypeId, prototypeTypeNameId] = fetchTypeIdByNameUngarded(type.prototype, + type.sourceId); + if (!prototypeId) + throw TypeNameDoesNotExists{}; + + updatePrototypeStatement.write(&type.typeId, &prototypeId); + } + } + + Utils::SmallStringView extractTypeName(const Storage::TypeName &name) const + { + return Utils::visit([](auto &&typeName) -> Utils::SmallStringView { return typeName.name; }, + name); + } + + Storage::TypeName createTypeName(long long typeIndex, Utils::SmallStringView name) const + { + switch (typeIndex) { + case 0: + return Utils::variant_alternative_t<0, Storage::TypeName>(name); + case 1: + return Utils::variant_alternative_t<1, Storage::TypeName>(name); + } + + return {}; + } + + class TypeIdAndTypeNameId + { + public: + TypeIdAndTypeNameId() = default; + TypeIdAndTypeNameId(long long typeId, long long typeNameId) + : typeId{typeId} + , typeNameId{typeNameId} + {} + + public: + TypeId typeId; + TypeNameId typeNameId; + }; + + TypeIdAndTypeNameId fetchTypeIdByNameUngarded(const Storage::TypeName &name, SourceId sourceId) + { struct Inspect { - TypeId operator()(const Storage::NativeType &nativeType) + auto operator()(const Storage::NativeType &nativeType) { - return storage.selectTypeIdByImportIdsAndNameStatement - .template value(static_cast(importIds.data()), - static_cast(importIds.size()), - nativeType.name); + return storage.selectTypeIdByImportIdsFromSourceIdAndNameStatement + .template value(&sourceId, nativeType.name); } - TypeId operator()(const Storage::ExportedType &exportedType) + auto operator()(const Storage::ExportedType &exportedType) { - return storage.selectTypeIdByImportIdsAndExportedNameStatement - .template value(static_cast(importIds.data()), - static_cast(importIds.size()), - exportedType.name); + return storage.selectTypeIdByImportIdsFromSourceIdAndExportedNameStatement + .template value(&sourceId, exportedType.name); } - TypeId operator()(const Storage::ExplicitExportedType &exportedType) + auto operator()(const Storage::ExplicitExportedType &exportedType) { return storage.selectTypeIdByImportIdAndExportedNameStatement - .template value(&exportedType.importId, exportedType.name); + .template value(&exportedType.importId, exportedType.name); } ProjectStorage &storage; - ImportIds &importIds; + SourceId sourceId; }; - auto typeId = Utils::visit(Inspect{*this, importIds}, name); - - if (typeId) - return typeId; - - throw TypeNameDoesNotExists{}; + return Utils::visit(Inspect{*this, sourceId}, name); } - using PropertyDeclarationViewTuple = std::tuple; - - PropertyDeclarationViewTuple fetchPropertyDeclarationByTypeNameAndName( - const Storage::TypeName &typeName, Utils::SmallStringView name, ImportIds &importIds) + class FetchPropertyDeclarationResult { - TypeId typeId = fetchTypeIdByNameUngarded(typeName, importIds); + public: + FetchPropertyDeclarationResult(long long propertyTypeId, + long long propertyDeclarationId, + long long propertyTraits) + : propertyTypeId{propertyTypeId} + , propertyDeclarationId{propertyDeclarationId} + , propertyTraits{propertyTraits} + {} + public: + TypeId propertyTypeId; + PropertyDeclarationId propertyDeclarationId; + long long propertyTraits; + }; + + FetchPropertyDeclarationResult fetchPropertyDeclarationByTypeIdAndNameUngarded( + TypeId typeId, Utils::SmallStringView name) + { auto propertyDeclaration = selectPropertyDeclarationByTypeIdAndNameStatement - .template value(&typeId, name); + .template optionalValue(&typeId, + name); - if (auto id = std::get(propertyDeclaration); id) - return propertyDeclaration; + if (propertyDeclaration) + return *propertyDeclaration; throw PropertyNameDoesNotExists{}; } @@ -947,10 +1237,11 @@ private: createSourceContextsTable(database); createSourcesTable(database); createTypesAndePropertyDeclarationsTables(database); - createExportedTypesTable(database); + createTypeNamesTable(database); createEnumerationsTable(database); createFunctionsTable(database); createSignalsTable(database); + createSourceImportsTable(database); transaction.commit(); @@ -1024,6 +1315,7 @@ private: Sqlite::ForeignKeyAction::NoAction, Sqlite::ForeignKeyAction::Restrict); propertyDeclarationTable.addColumn("propertyTraits"); + propertyDeclarationTable.addColumn("propertyTypeNameId"); auto &aliasPropertyDeclarationIdColumn = propertyDeclarationTable.addForeignKeyColumn( "aliasPropertyDeclarationId", propertyDeclarationTable, @@ -1038,17 +1330,18 @@ private: } } - void createExportedTypesTable(Database &database) + void createTypeNamesTable(Database &database) { Sqlite::Table table; table.setUseIfNotExists(true); - table.setUseWithoutRowId(true); - table.setName("exportedTypes"); + table.setName("typeNames"); + table.addColumn("typeNameId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); auto &importIdColumn = table.addColumn("importId"); auto &nameColumn = table.addColumn("name"); + auto &kindColumn = table.addColumn("kind"); table.addColumn("typeId"); - table.addPrimaryKeyContraint({importIdColumn, nameColumn}); + table.addUniqueIndex({importIdColumn, nameColumn, kindColumn}); table.initialize(database); } @@ -1131,6 +1424,20 @@ private: table.initialize(database); } + + void createSourceImportsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setUseWithoutRowId(true); + table.setName("sourceImports"); + auto &sourceIdColumn = table.addColumn("sourceId"); + auto &importIdColumn = table.addColumn("importId"); + + table.addPrimaryKeyContraint({sourceIdColumn, importIdColumn}); + + table.initialize(database); + } }; public: @@ -1142,10 +1449,10 @@ public: "CONFLICT DO UPDATE SET prototypeId=excluded.prototypeId, " "accessSemantics=excluded.accessSemantics, sourceId=excluded.sourceId RETURNING typeId", database}; - WriteStatement updatePrototypeStatement{ - "UPDATE types SET prototypeId=nullif(?2, -1) WHERE typeId=?1", database}; + WriteStatement updatePrototypeStatement{"UPDATE types SET prototypeId=?2 WHERE typeId=?1", + database}; mutable ReadStatement<1> selectTypeIdByExportedNameStatement{ - "SELECT typeId FROM exportedTypes WHERE name=?1", database}; + "SELECT typeId FROM typeNames WHERE name=?1 AND kind=1", database}; mutable ReadStatement<1> selectPrototypeIdStatement{ "WITH RECURSIVE " " typeSelection(typeId) AS (" @@ -1156,33 +1463,36 @@ public: database}; mutable ReadStatement<1> selectPropertyDeclarationIdByTypeIdAndNameStatement{ "WITH RECURSIVE " - " typeSelection(typeId) AS (" - " VALUES(?1) " + " typeSelection(typeId, level) AS (" + " VALUES(?1, 0) " " UNION ALL " - " SELECT prototypeId FROM types JOIN typeSelection USING(typeId)) " + " SELECT prototypeId, typeSelection.level+1 FROM types JOIN typeSelection " + " USING(typeId)) " "SELECT propertyDeclarationId FROM propertyDeclarations JOIN typeSelection USING(typeId) " - " WHERE name=?2 LIMIT 1", + " WHERE name=?2 ORDER BY level LIMIT 1", database}; mutable ReadStatement<3> selectPropertyDeclarationByTypeIdAndNameStatement{ "WITH RECURSIVE " - " typeSelection(typeId) AS (" - " VALUES(?1) " + " typeSelection(typeId, level) AS (" + " VALUES(?1, 0) " " UNION ALL " - " SELECT prototypeId FROM types JOIN typeSelection USING(typeId)) " - "SELECT propertyTypeId, propertyDeclarationId, propertyTraits FROM propertyDeclarations " - " JOIN typeSelection USING(typeId) " - " WHERE name=?2 LIMIT 1", + " SELECT prototypeId, typeSelection.level+1 FROM types JOIN typeSelection " + " USING(typeId)) " + "SELECT propertyTypeId, propertyDeclarationId, propertyTraits " + " FROM propertyDeclarations JOIN typeSelection USING(typeId) " + " WHERE name=?2 ORDER BY level LIMIT 1", database}; - WriteStatement upsertExportedTypesStatement{"INSERT INTO exportedTypes(importId, name, typeId) " - "VALUES(?1, ?2, ?3) ON CONFLICT DO NOTHING", - database}; + WriteStatement upsertTypeNamesStatement{"INSERT INTO typeNames(importId, name, typeId, kind) " + "VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING", + database}; mutable ReadStatement<1> selectPrototypeIdsStatement{ "WITH RECURSIVE " - " typeSelection(typeId) AS (" - " VALUES(?1) " + " typeSelection(typeId, level) AS (" + " VALUES(?1, 0) " " UNION ALL " - " SELECT prototypeId FROM types JOIN typeSelection USING(typeId)) " - "SELECT typeId FROM typeSelection", + " SELECT prototypeId, typeSelection.level+1 FROM types JOIN typeSelection " + " USING(typeId)) " + "SELECT typeId FROM typeSelection ORDER BY level DESC", database}; mutable ReadStatement<1> selectSourceContextIdFromSourceContextsBySourceContextPathStatement{ "SELECT sourceContextId FROM sourceContexts WHERE sourceContextPath = ?", database}; @@ -1204,12 +1514,16 @@ public: "SELECT sourceName, sourceContextId, sourceId FROM sources", database}; mutable ReadStatement<1> selectTypeIdByImportIdsAndNameStatement{ "SELECT typeId FROM types WHERE importId IN carray(?1, ?2, 'int64') AND name=?3", database}; + mutable ReadStatement<2> selectTypeIdByImportIdsFromSourceIdAndNameStatement{ + "SELECT typeId, typeNameId FROM typeNames JOIN sourceImports USING(importId) WHERE name=?2 " + "AND kind=0 AND sourceImports.sourceId=?1", + database}; mutable ReadStatement<5> selectTypeByTypeIdStatement{ "SELECT importId, name, (SELECT name FROM types WHERE typeId=outerTypes.prototypeId), " "accessSemantics, ifnull(sourceId, -1) FROM types AS outerTypes WHERE typeId=?", database}; mutable ReadStatement<1> selectExportedTypesByTypeIdStatement{ - "SELECT name FROM exportedTypes WHERE typeId=?", database}; + "SELECT name FROM typeNames WHERE typeId=? AND kind=1", database}; mutable ReadStatement<6> selectTypesStatement{ "SELECT importId, name, typeId, (SELECT name FROM types WHERE " "typeId=outerTypes.prototypeId), accessSemantics, ifnull(sourceId, -1) FROM types AS " @@ -1218,8 +1532,7 @@ public: ReadStatement<1> selectNotUpdatedTypesInSourcesStatement{ "SELECT typeId FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN carray(?2))", database}; - WriteStatement deleteExportTypesByTypeIdStatement{"DELETE FROM exportedTypes WHERE typeId=?", - database}; + WriteStatement deleteTypeNamesByTypeIdStatement{"DELETE FROM typeNames WHERE typeId=?", database}; WriteStatement deleteEnumerationDeclarationByTypeIdStatement{ "DELETE FROM enumerationDeclarations WHERE typeId=?", database}; WriteStatement deletePropertyDeclarationByTypeIdStatement{ @@ -1233,18 +1546,23 @@ public: "SELECT name, (SELECT name FROM types WHERE typeId=propertyDeclarations.propertyTypeId)," "propertyTraits FROM propertyDeclarations WHERE typeId=?", database}; - ReadStatement<4> selectPropertyDeclarationsForTypeIdStatement{ - "SELECT name, propertyTraits, propertyTypeId, propertyDeclarationId FROM " - "propertyDeclarations WHERE typeId=? AND aliasPropertyDeclarationId IS NULL ORDER BY " + ReadStatement<5> selectPropertyDeclarationsForTypeIdStatement{ + "SELECT name, propertyTraits, propertyTypeId, propertyTypeNameId, propertyDeclarationId " + "FROM propertyDeclarations WHERE typeId=? AND aliasPropertyDeclarationId IS NULL ORDER BY " "name", database}; - WriteStatement insertPropertyDeclarationStatement{ - "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId, propertyTraits) " - "VALUES(?1, ?2, ?3, ?4)", + ReadWriteStatement<1> insertPropertyDeclarationStatement{ + "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId, propertyTraits, " + "propertyTypeNameId, aliasPropertyDeclarationId) VALUES(?1, ?2, ?3, ?4, ?5, NULL) " + "RETURNING propertyDeclarationId", database}; WriteStatement updatePropertyDeclarationStatement{ + "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTraits=?3, " + "propertyTypeNameId=?4 WHERE propertyDeclarationId=?1", + database}; + WriteStatement updatePropertyDeclarationWithAliasIdStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTraits=?3 WHERE " - "propertyDeclarationId=?1 OR aliasPropertyDeclarationId=?1", + "aliasPropertyDeclarationId=?1", database}; WriteStatement deletePropertyDeclarationStatement{ "DELETE FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; @@ -1252,13 +1570,14 @@ public: "SELECT name, propertyDeclarationId, aliasPropertyDeclarationId FROM propertyDeclarations " "WHERE typeId=? AND aliasPropertyDeclarationId IS NOT NULL ORDER BY name", database}; - WriteStatement insertPropertyDeclarationWithAliasStatement{ - "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId, propertyTraits, " - "aliasPropertyDeclarationId) VALUES(?1, ?2, ?3, ?4, ?5) ", - database}; WriteStatement updatePropertyDeclarationWithAliasStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTraits=?3, " - "aliasPropertyDeclarationId=?4 WHERE propertyDeclarationId=?1", + "propertyTypeNameId=?4, aliasPropertyDeclarationId=?5 WHERE propertyDeclarationId=?1", + database}; + WriteStatement insertPropertyDeclarationWithAliasStatement{ + "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId, propertyTraits, " + "propertyTypeNameId, aliasPropertyDeclarationId) " + "VALUES(?1, ?2, ?3, ?4, ?5, ?6)", database}; mutable ReadStatement<4> selectFunctionDeclarationsForTypeIdStatement{ "SELECT name, returnTypeName, signature, functionDeclarationId FROM " @@ -1351,10 +1670,15 @@ public: mutable ReadStatement<1> selectTypeIdByImportIdAndNameStatement{ "SELECT typeId FROM types WHERE importId=?1 and name=?2", database}; mutable ReadStatement<1> selectTypeIdByImportIdsAndExportedNameStatement{ - "SELECT typeId FROM exportedTypes WHERE importId IN carray(?1, ?2, 'int64') AND name=?3", + "SELECT typeId FROM typeNames WHERE importId IN carray(?1, ?2, 'int64') AND name=?3 AND " + "kind=1", database}; - mutable ReadStatement<1> selectTypeIdByImportIdAndExportedNameStatement{ - "SELECT typeId FROM exportedTypes WHERE importId=?1 AND name=?2", database}; + mutable ReadStatement<2> selectTypeIdByImportIdsFromSourceIdAndExportedNameStatement{ + "SELECT typeId, typeNameId FROM typeNames JOIN sourceImports USING(importId) WHERE name=?2 " + "AND kind=1 AND sourceImports.sourceId=?1", + database}; + mutable ReadStatement<2> selectTypeIdByImportIdAndExportedNameStatement{ + "SELECT typeId, typeNameId FROM typeNames WHERE importId=?1 AND name=?2 AND kind=1", database}; mutable ReadStatement<1> fetchImportDependencyIdsStatement{ "WITH RECURSIVE " " importIds(importId) AS (" @@ -1363,6 +1687,59 @@ public: " SELECT parentImportId FROM importDependencies JOIN importIds USING(importId)) " "SELECT importId FROM importIds", database}; + mutable ReadStatement<1> selectImportIdsForSourceIdStatement{ + "SELECT importId FROM sourceImports WHERE sourceId=? ORDER BY importId", database}; + WriteStatement insertImportIdForSourceIdStatement{ + "INSERT INTO sourceImports(sourceId, importId) VALUES (?1, ?2)", database}; + WriteStatement deleteImportIdForSourceIdStatement{ + "DELETE FROM sourceImports WHERE sourceId=?1 AND importId=?2", database}; + ReadStatement<1> selectPropertyDeclarationIdPrototypeChainDownStatement{ + "WITH RECURSIVE " + " typeSelection(typeId, level) AS (" + " SELECT prototypeId, 0 FROM types WHERE typeId=?1 AND prototypeId IS NOT NULL" + " UNION ALL " + " SELECT prototypeId, typeSelection.level+1 FROM types JOIN typeSelection " + " USING(typeId) WHERE prototypeId IS NOT NULL)" + "SELECT propertyDeclarationId FROM typeSelection JOIN propertyDeclarations " + " USING(typeId) WHERE name=?2 ORDER BY level LIMIT 1", + database}; + WriteStatement updateAliasPropertyDeclarationStatement{ + "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTraits=?3, " + "aliasPropertyDeclarationId=?4 WHERE aliasPropertyDeclarationId=?1", + database}; + WriteStatement updateAliasPropertyDeclarationByAliasPropertyDeclarationIdStatement{ + "UPDATE propertyDeclarations SET propertyTypeId=new.propertyTypeId, " + "propertyTraits=new.propertyTraits, aliasPropertyDeclarationId=?1 FROM (SELECT " + "propertyTypeId, propertyTraits FROM propertyDeclarations WHERE propertyDeclarationId=?1) " + "AS new WHERE aliasPropertyDeclarationId=?2", + database}; + WriteStatement updateAliasPropertyDeclarationToNullStatement{ + "UPDATE propertyDeclarations SET aliasPropertyDeclarationId=NULL, propertyTypeId=NULL, " + "propertyTraits=NULL WHERE propertyDeclarationId=?", + database}; + ReadStatement<4> selectAliasPropertiesDeclarationForPropertiesWithTypeIdStatement{ + "SELECT alias.propertyDeclarationId, alias.propertyTypeNameId, alias.typeId, " + "target.propertyDeclarationId FROM propertyDeclarations AS alias JOIN propertyDeclarations " + "AS target ON alias.aliasPropertyDeclarationId=target.propertyDeclarationId WHERE " + "alias.propertyTypeId=?1", + database}; + ReadWriteStatement<2> updatesPropertyDeclarationPropertyTypeToNullStatement{ + "UPDATE propertyDeclarations SET propertyTypeId=NULL WHERE propertyTypeId=?1 AND " + "aliasPropertyDeclarationId IS NULL RETURNING propertyDeclarationId, propertyTypeNameId", + database}; + ReadStatement<1> selectSourceIdForTypeIdStatement{"SELECT sourceId FROM types WHERE typeId=?", + database}; + ReadStatement<2> selectTypeNameStatement{"SELECT name, kind FROM typeNames WHERE typeNameId=?", + database}; + ReadStatement<1> selectPropertyNameStatement{ + "SELECT name FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; + WriteStatement updatePropertyDeclarationTypeStatement{ + "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTypeNameId=?3 WHERE " + "propertyDeclarationId=?1", + database}; + ReadStatement<1> selectPropertyDeclarationIdStatement{ + "SELECT propertyDeclarationId FROM propertyDeclarations WHERE propertyDeclarationId=?", + database}; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 444d25f6bd8..902e0ec56fd 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -43,6 +43,8 @@ enum class PropertyDeclarationTraits : unsigned int { IsList = 1 << 2 }; +enum class TypeNameKind { Native = 0, Exported = 1 }; + constexpr PropertyDeclarationTraits operator|(PropertyDeclarationTraits first, PropertyDeclarationTraits second) { @@ -372,13 +374,12 @@ using PropertyDeclarations = std::vector; class PropertyDeclarationView { public: - explicit PropertyDeclarationView(Utils::SmallStringView name, - int traits, - long long typeId, - long long id) + explicit PropertyDeclarationView( + Utils::SmallStringView name, int traits, long long typeId, long long typeNameId, long long id) : name{name} , traits{static_cast(traits)} , typeId{typeId} + , typeNameId{typeNameId} , id{id} {} @@ -387,6 +388,7 @@ public: Utils::SmallStringView name; PropertyDeclarationTraits traits = {}; TypeId typeId; + TypeNameId typeNameId; PropertyDeclarationId id; }; @@ -419,7 +421,7 @@ public: {} public: - Utils::SmallString name; + Utils::SmallStringView name; PropertyDeclarationId id; PropertyDeclarationId aliasId; }; @@ -443,7 +445,6 @@ public: TypeId typeId = TypeId{}) : typeName{typeName} , prototype{std::move(prototype)} - , importIds{std::move(importIds)} , exportedTypes{std::move(exportedTypes)} , propertyDeclarations{std::move(propertyDeclarations)} , functionDeclarations{std::move(functionDeclarations)} @@ -487,7 +488,6 @@ public: Utils::SmallString typeName; TypeName prototype; Utils::SmallString attachedType; - ImportIds importIds; ExportedTypes exportedTypes; PropertyDeclarations propertyDeclarations; FunctionDeclarations functionDeclarations; @@ -503,6 +503,22 @@ public: using Types = std::vector; +class Document +{ +public: + explicit Document() = default; + explicit Document(SourceId sourceId, ImportIds importIds) + : importIds{std::move(importIds)} + , sourceId{sourceId} + {} + +public: + ImportIds importIds; + SourceId sourceId; +}; + +using Documents = std::vector; + class BasicImport { public: diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index 41b73da57b1..ffc4f28164f 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -195,7 +195,6 @@ protected: ProjectStorageMock storage{databaseMock, true}; ReadWriteStatement<1> &upsertTypeStatement = storage.upsertTypeStatement; ReadStatement<1> &selectTypeIdByExportedNameStatement = storage.selectTypeIdByExportedNameStatement; - WriteStatement &upsertExportedTypesStatement = storage.upsertExportedTypesStatement; ReadStatement<1> &selectSourceContextIdFromSourceContextsBySourceContextPathStatement = storage.selectSourceContextIdFromSourceContextsBySourceContextPathStatement; ReadStatement<1> &selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment @@ -468,6 +467,11 @@ protected: sourceId3 = sourcePathCache.sourceId(path3); sourceId4 = sourcePathCache.sourceId(path4); + storage.synchronizeDocuments({Storage::Document{sourceId1, importIds}, + Storage::Document{sourceId2, importIds}, + Storage::Document{sourceId3, importIds}, + Storage::Document{sourceId4, importIds}}); + return Storage::Types{ Storage::Type{ importId2, @@ -869,7 +873,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddsNewTypesThrowsWithWrongExport TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddsNewTypesWithMissingImportAndExportedPrototypeName) { Storage::Types types{createTypes()}; - types[0].importIds = {importId1}; + storage.synchronizeDocuments({Storage::Document{sourceId1, {importId1}}}); types[1].prototype = Storage::ExportedType{"Object"}; ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1, sourceId2}), @@ -879,7 +883,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddsNewTypesWithMissingImportAndE TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddsNewTypesWithMissingImport) { Storage::Types types{createTypes()}; - types[0].importIds = {importId1}; + storage.synchronizeDocuments({Storage::Document{sourceId1, {importId1}}}); ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1, sourceId2}), QmlDesigner::TypeNameDoesNotExists); @@ -1154,7 +1158,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddPropertyDeclarationsWithMissingImportIdsForNativeTypes) { Storage::Types types{createTypes()}; - types[0].importIds = {importId2}; + storage.synchronizeDocuments({Storage::Document{sourceId1, {importId2}}}); types[0].propertyDeclarations.pop_back(); ASSERT_THROW(storage.synchronizeTypes(types, {}), QmlDesigner::TypeNameDoesNotExists); @@ -1164,7 +1168,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddPropertyDeclarationsWithMissingImportIdsForExportedTypes) { Storage::Types types{createTypes()}; - types[0].importIds = {importId1}; + storage.synchronizeDocuments({Storage::Document{sourceId1, {importId1}}}); types[0].propertyDeclarations[0].typeName = Storage::ExportedType{"Obj"}; ASSERT_THROW(storage.synchronizeTypes(types, {}), QmlDesigner::TypeNameDoesNotExists); @@ -1414,7 +1418,7 @@ TEST_F(ProjectStorageSlowTest, BreakingPropertyDeclarationTypeDependencyByDeleti types.pop_back(); ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1, sourceId2}), - Sqlite::ConstraintPreventsModification); + QmlDesigner::TypeNameDoesNotExists); } TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddFunctionDeclarations) @@ -2702,4 +2706,137 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeAndAliasPropertyDeclara Storage::PropertyDeclarationTraits::IsList)))))); } +TEST_F(ProjectStorageSlowTest, UpdateAliasPropertyIfPropertyIsOverloaded) +{ + Storage::Types types{createTypesWithAliases()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[0].propertyDeclarations.push_back( + Storage::PropertyDeclaration{"objects", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}); + + storage.synchronizeTypes({types[0]}, {sourceId1}); + + ASSERT_THAT( + storage.fetchTypes(), + Contains( + AllOf(IsStorageType(importId2, + "QAliasItem", + Storage::NativeType{"QQuickItem"}, + TypeAccessSemantics::Reference, + sourceId3), + Field(&Storage::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, AliasPropertyIsOverloaded) +{ + Storage::Types types{createTypesWithAliases()}; + types[0].propertyDeclarations.push_back( + Storage::PropertyDeclaration{"objects", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}); + + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + + ASSERT_THAT( + storage.fetchTypes(), + Contains( + AllOf(IsStorageType(importId2, + "QAliasItem", + Storage::NativeType{"QQuickItem"}, + TypeAccessSemantics::Reference, + sourceId3), + Field(&Storage::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, UpdateAliasPropertyIfOverloadedPropertyIsRemoved) +{ + Storage::Types types{createTypesWithAliases()}; + types[0].propertyDeclarations.push_back( + Storage::PropertyDeclaration{"objects", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[0].propertyDeclarations.pop_back(); + + storage.synchronizeTypes({types[0]}, {sourceId1}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(AllOf( + IsStorageType(importId2, + "QAliasItem", + Storage::NativeType{"QQuickItem"}, + TypeAccessSemantics::Reference, + sourceId3), + Field(&Storage::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, RelinkAliasProperty) +{ + Storage::Types types{createTypesWithAliases()}; + types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[3].importId = importId2; + + storage.synchronizeTypes({types[3]}, {sourceId4}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(AllOf( + IsStorageType(importId2, + "QAliasItem", + Storage::NativeType{"QQuickItem"}, + TypeAccessSemantics::Reference, + sourceId3), + Field(&Storage::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + Storage::NativeType{"QObject2"}, + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + } // namespace