diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index f877afeb6fc..fb2e7941c0b 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) + synchronizeAliasPropertyDeclarationsRemoval(type); + for (auto &&type : types) syncType(type); @@ -111,14 +114,6 @@ public: 16, static_cast(importIds.data()), static_cast(importIds.size())); } - PropertyDeclarationId upsertPropertyDeclaration(TypeId typeId, - Utils::SmallStringView name, - TypeId propertyTypeId) - { - return upsertPropertyDeclarationStatement - .template valueWithTransaction(&typeId, name, &propertyTypeId, 0); - } - PropertyDeclarationId fetchPropertyDeclarationByTypeIdAndName(TypeId typeId, Utils::SmallStringView name) { @@ -488,7 +483,7 @@ private: auto insert = [&](const Storage::PropertyDeclaration &value) { auto propertyTypeId = fetchTypeIdByNameUngarded(value.typeName, importIds); - upsertPropertyDeclarationStatement.write(&typeId, + insertPropertyDeclarationStatement.write(&typeId, value.name, &propertyTypeId, static_cast(value.traits)); @@ -513,6 +508,35 @@ private: Sqlite::insertUpdateDelete(range, propertyDeclarations, compareKey, insert, update, remove); } + void synchronizeAliasPropertyDeclarationsRemoval(Storage::Type &type) + { + auto &aliasDeclarations = type.aliasDeclarations; + TypeId typeId = type.typeId; + + std::sort(aliasDeclarations.begin(), aliasDeclarations.end(), [](auto &&first, auto &&second) { + return Sqlite::compare(first.name, second.name) < 0; + }); + + auto range = selectPropertyDeclarationsWithAliasForTypeIdStatement + .template range(&typeId); + + auto compareKey = [](const Storage::AliasPropertyDeclarationView &view, + const Storage::AliasPropertyDeclaration &value) { + return Sqlite::compare(view.name, value.name); + }; + + auto insert = [&](const Storage::AliasPropertyDeclaration &) {}; + + auto update = [&](const Storage::AliasPropertyDeclarationView &, + const Storage::AliasPropertyDeclaration &) {}; + + auto remove = [&](const Storage::AliasPropertyDeclarationView &view) { + deletePropertyDeclarationStatement.write(&view.id); + }; + + Sqlite::insertUpdateDelete(range, aliasDeclarations, compareKey, insert, update, remove); + } + void synchronizeAliasPropertyDeclarations(Storage::Type &type) { auto &aliasDeclarations = type.aliasDeclarations; @@ -556,9 +580,7 @@ private: &aliasId); }; - auto remove = [&](const Storage::AliasPropertyDeclarationView &view) { - deletePropertyDeclarationStatement.write(&view.id); - }; + auto remove = [&](const Storage::AliasPropertyDeclarationView &) {}; Sqlite::insertUpdateDelete(range, aliasDeclarations, compareKey, insert, update, remove); } @@ -1002,9 +1024,15 @@ private: Sqlite::ForeignKeyAction::NoAction, Sqlite::ForeignKeyAction::Restrict); propertyDeclarationTable.addColumn("propertyTraits"); - propertyDeclarationTable.addColumn("aliasPropertyDeclarationId"); + auto &aliasPropertyDeclarationIdColumn = propertyDeclarationTable.addForeignKeyColumn( + "aliasPropertyDeclarationId", + propertyDeclarationTable, + Sqlite::ForeignKeyAction::NoAction, + Sqlite::ForeignKeyAction::Restrict); propertyDeclarationTable.addUniqueIndex({typeIdColumn, nameColumn}); + propertyDeclarationTable.addIndex({aliasPropertyDeclarationIdColumn}, + "aliasPropertyDeclarationId IS NOT NULL"); propertyDeclarationTable.initialize(database); } @@ -1141,8 +1169,8 @@ public: " VALUES(?1) " " UNION ALL " " SELECT prototypeId FROM types JOIN typeSelection USING(typeId)) " - "SELECT typeId, propertyDeclarationId, propertyTraits FROM propertyDeclarations JOIN " - "typeSelection USING(typeId) " + "SELECT propertyTypeId, propertyDeclarationId, propertyTraits FROM propertyDeclarations " + " JOIN typeSelection USING(typeId) " " WHERE name=?2 LIMIT 1", database}; WriteStatement upsertExportedTypesStatement{"INSERT INTO exportedTypes(importId, name, typeId) " @@ -1210,14 +1238,13 @@ public: "propertyDeclarations WHERE typeId=? AND aliasPropertyDeclarationId IS NULL ORDER BY " "name", database}; - WriteStatement upsertPropertyDeclarationStatement{ + WriteStatement insertPropertyDeclarationStatement{ "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId, propertyTraits) " - "VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO UPDATE SET propertyTypeId=excluded.propertyTypeId, " - "propertyTraits=excluded.propertyTraits, aliasPropertyDeclarationId=NULL", + "VALUES(?1, ?2, ?3, ?4)", database}; WriteStatement updatePropertyDeclarationStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2, propertyTraits=?3 WHERE " - "propertyDeclarationId=?1", + "propertyDeclarationId=?1 OR aliasPropertyDeclarationId=?1", database}; WriteStatement deletePropertyDeclarationStatement{ "DELETE FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index c250b4c81ac..385fb0e6714 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -529,7 +529,7 @@ protected: "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1, + sourceId3, importIds, {Storage::ExportedType{"AliasItem"}}}); types.back().propertyDeclarations.push_back( @@ -541,6 +541,19 @@ protected: types.back().aliasDeclarations.push_back( Storage::AliasPropertyDeclaration{"objects", Storage::NativeType{"QQuickItem"}, "objects"}); + types.push_back( + Storage::Type{importId3, + "QObject2", + Storage::NativeType{}, + TypeAccessSemantics::Reference, + sourceId4, + importIds, + {Storage::ExportedType{"Object2"}, Storage::ExportedType{"Obj2"}}}); + types[3].propertyDeclarations.push_back( + Storage::PropertyDeclaration{"objects", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList}); + return types; } @@ -1408,7 +1421,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddFunctionDeclarations) { Storage::Types types{createTypes()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf(IsStorageType(importId2, @@ -2331,7 +2344,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarations) { Storage::Types types{createTypesWithAliases()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf( @@ -2339,7 +2352,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarations) "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2357,9 +2370,9 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarations) TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarationsAgain) { Storage::Types types{createTypesWithAliases()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf( @@ -2367,7 +2380,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarationsAgain) "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2385,10 +2398,10 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarationsAgain) TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveAliasDeclarations) { Storage::Types types{createTypesWithAliases()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); types[2].aliasDeclarations.pop_back(); - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes({types[2]}, {sourceId3}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf( @@ -2396,7 +2409,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveAliasDeclarations) "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2413,34 +2426,24 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarationsThrowsForWron Storage::Types types{createTypesWithAliases()}; types[2].aliasDeclarations[0].aliasTypeName = Storage::NativeType{"QQuickItemWrong"}; - ASSERT_THROW(storage.synchronizeTypes(types, {}), QmlDesigner::TypeNameDoesNotExists); + ASSERT_THROW(storage.synchronizeTypes({types[2]}, {sourceId4}), + QmlDesigner::TypeNameDoesNotExists); } TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddAliasDeclarationsThrowsForWrongPropertyName) { Storage::Types types{createTypesWithAliases()}; types[2].aliasDeclarations[0].aliasPropertyName = "childrenWrong"; - ASSERT_THROW(storage.synchronizeTypes(types, {}), QmlDesigner::PropertyNameDoesNotExists); + ASSERT_THROW(storage.synchronizeTypes(types, {sourceId4}), QmlDesigner::PropertyNameDoesNotExists); } TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsTypeName) { Storage::Types types{createTypesWithAliases()}; - types.push_back(Storage::Type{importId1, - "QObject2", - Storage::NativeType{}, - TypeAccessSemantics::Reference, - sourceId2, - importIds, - {Storage::ExportedType{"Object2"}, Storage::ExportedType{"Obj2"}}}); - types[3].propertyDeclarations.push_back( - Storage::PropertyDeclaration{"objects", - Storage::NativeType{"QObject"}, - Storage::PropertyDeclarationTraits::IsList}); - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); types[2].aliasDeclarations[1].aliasTypeName = Storage::ExportedType{"Obj2"}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes({types[2]}, {sourceId3}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf( @@ -2448,7 +2451,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsTypeName) "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2456,7 +2459,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsTypeName) Storage::PropertyDeclarationTraits::IsList | Storage::PropertyDeclarationTraits::IsReadOnly), IsPropertyDeclaration("objects", - Storage::NativeType{"QObject2"}, + Storage::NativeType{"QObject"}, Storage::PropertyDeclarationTraits::IsList), IsPropertyDeclaration("data", Storage::NativeType{"QObject"}, @@ -2466,10 +2469,10 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsTypeName) TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsPropertyName) { Storage::Types types{createTypesWithAliases()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); types[2].aliasDeclarations[1].aliasPropertyName = "children"; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes({types[2]}, {sourceId3}); ASSERT_THAT( storage.fetchTypes(), @@ -2478,7 +2481,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsPropertyNa "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2497,7 +2500,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsPropertyNa TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsToPropertyDeclaration) { Storage::Types types{createTypesWithAliases()}; - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); types[2].aliasDeclarations.pop_back(); types[2].propertyDeclarations.push_back( Storage::PropertyDeclaration{"objects", @@ -2505,7 +2508,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsToProperty Storage::PropertyDeclarationTraits::IsList | Storage::PropertyDeclarationTraits::IsReadOnly}); - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes({types[2]}, {sourceId3}); ASSERT_THAT( storage.fetchTypes(), @@ -2514,7 +2517,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasDeclarationsToProperty "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2540,9 +2543,9 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangePropertyDeclarationsToAlias Storage::NativeType{"QQuickItem"}, Storage::PropertyDeclarationTraits::IsList | Storage::PropertyDeclarationTraits::IsReadOnly}); - storage.synchronizeTypes(typesChanged, {}); + storage.synchronizeTypes(typesChanged, {sourceId1, sourceId2, sourceId3, sourceId4}); - storage.synchronizeTypes(types, {}); + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); ASSERT_THAT(storage.fetchTypes(), Contains(AllOf( @@ -2550,7 +2553,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangePropertyDeclarationsToAlias "QAliasItem", Storage::NativeType{"QQuickItem"}, TypeAccessSemantics::Reference, - sourceId1), + sourceId3), Field(&Storage::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2565,4 +2568,138 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangePropertyDeclarationsToAlias Storage::PropertyDeclarationTraits::IsList)))))); } +TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasTargetPropertyDeclarationTraits) +{ + Storage::Types types{createTypesWithAliases()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[1].propertyDeclarations[0].traits = Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly; + + storage.synchronizeTypes({types[1]}, {sourceId2}); + + 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 + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeTypesChangeAliasTargetPropertyDeclarationTypeName) +{ + Storage::Types types{createTypesWithAliases()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[1].propertyDeclarations[0].typeName = Storage::ExportedType{"Item"}; + + storage.synchronizeTypes({types[1]}, {sourceId2}); + + 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), + IsPropertyDeclaration("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemovePropertyDeclarationWithAnAliasThrows) +{ + Storage::Types types{createTypesWithAliases()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[1].propertyDeclarations.pop_back(); + + ASSERT_THROW(storage.synchronizeTypes({types[1]}, {sourceId2}), + Sqlite::ConstraintPreventsModification); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemovePropertyDeclarationAndAlias) +{ + Storage::Types types{createTypesWithAliases()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[1].propertyDeclarations.pop_back(); + types[2].aliasDeclarations.pop_back(); + + storage.synchronizeTypes({types[1], types[2]}, {sourceId2, sourceId3}); + + 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("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeWithAliasTargetPropertyDeclarationThrows) +{ + Storage::Types types{createTypesWithAliases()}; + types[2].aliasDeclarations[1].aliasTypeName = Storage::ExportedType{"Object2"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + + ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), Sqlite::ConstraintPreventsModification); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeAndAliasPropertyDeclaration) +{ + Storage::Types types{createTypesWithAliases()}; + types[2].aliasDeclarations[1].aliasTypeName = Storage::ExportedType{"Object2"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4}); + types[2].aliasDeclarations.pop_back(); + + storage.synchronizeTypes({types[0], types[2]}, {sourceId1, sourceId3}); + + 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("data", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsList)))))); +} + } // namespace