diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 92d7aefc620..52d5f210c21 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -472,6 +472,21 @@ private: SourceId sourceId; }; + class Prototype + { + public: + explicit Prototype(TypeId typeId, Storage::TypeName prototypeName, SourceId sourceId) + : typeId{typeId} + , typeName{std::move(prototypeName)} + , sourceId{sourceId} + {} + + public: + TypeId typeId; + Storage::TypeName typeName; + SourceId sourceId; + }; + Storage::TypeName fetchTypeName(TypeNameId typeNameId) { Storage::TypeName typeName; @@ -530,12 +545,27 @@ private: updatesPropertyDeclarationPropertyTypeToNullStatement.readCallback(callback, &typeId); } + void handlePrototypes(TypeId prototypeId, std::vector &relinkablePrototypes) + { + auto callback = [&](long long typeId, long long prototypeNameId, int sourceId) { + relinkablePrototypes.emplace_back(TypeId{typeId}, + fetchTypeName(TypeNameId{prototypeNameId}), + SourceId{sourceId}); + + return Sqlite::CallbackControl::Continue; + }; + + updatePrototypeToNullStatement.readCallback(callback, &prototypeId); + } + void deleteType(TypeId typeId, std::vector &relinkableAliasPropertyDeclarations, - std::vector &relinkablePropertyDeclarations) + std::vector &relinkablePropertyDeclarations, + std::vector &relinkablePrototypes) { handlePropertyDeclarationWithPropertyType(typeId, relinkablePropertyDeclarations); handleAliasPropertyDeclarationsWithPropertyType(typeId, relinkableAliasPropertyDeclarations); + handlePrototypes(typeId, relinkablePrototypes); deleteTypeNamesByTypeIdStatement.write(&typeId); deleteEnumerationDeclarationByTypeIdStatement.write(&typeId); deletePropertyDeclarationByTypeIdStatement.write(&typeId); @@ -544,7 +574,8 @@ private: deleteTypeStatement.write(&typeId); } - void relinkAliasPropertyDeclaration(const std::vector &aliasPropertyDeclarations) + void relinkAliasPropertyDeclarations( + const std::vector &aliasPropertyDeclarations) { for (const AliasPropertyDeclaration &alias : aliasPropertyDeclarations) { auto [typeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(alias.aliasTypeName, @@ -571,7 +602,7 @@ private: } } - void relinkPropertyDeclaration(const std::vector &relinkablePropertyDeclaration) + void relinkPropertyDeclarations(const std::vector &relinkablePropertyDeclaration) { for (const PropertyDeclaration &property : relinkablePropertyDeclaration) { auto [propertyTypeId, propertyTypeNameId] = fetchTypeIdByNameUngarded(property.typeName, @@ -593,10 +624,30 @@ private: } } + void relinkPrototypes(std::vector relinkablePrototypes) + { + for (const Prototype &prototype : relinkablePrototypes) { + auto [prototypeId, prototypeNameId] = fetchTypeIdByNameUngarded(prototype.typeName, + prototype.sourceId); + + if (!prototypeId) { + auto hasTypeId = selectPropertyDeclarationIdStatement.template optionalValue( + &prototype.typeId); + if (hasTypeId) + throw TypeNameDoesNotExists{}; + + continue; + } + + updateTypePrototypeStatement.write(&prototype.typeId, &prototypeId, &prototypeNameId); + } + } + void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &sourceIds) { std::vector relinkableAliasPropertyDeclarations; std::vector relinkablePropertyDeclarations; + std::vector relinkablePrototypes; auto updatedTypeIdValues = Utils::transform(updatedTypeIds, [](TypeId typeId) { return &typeId; @@ -609,7 +660,8 @@ private: auto callback = [&](long long typeId) { deleteType(TypeId{typeId}, relinkableAliasPropertyDeclarations, - relinkablePropertyDeclarations); + relinkablePropertyDeclarations, + relinkablePrototypes); return Sqlite::CallbackControl::Continue; }; @@ -617,17 +669,22 @@ private: Utils::span(sourceIdValues), Utils::span(updatedTypeIdValues)); - relinkPropertyDeclaration(relinkablePropertyDeclarations); - relinkAliasPropertyDeclaration(relinkableAliasPropertyDeclarations); + relinkPropertyDeclarations(relinkablePropertyDeclarations); + relinkAliasPropertyDeclarations(relinkableAliasPropertyDeclarations); + relinkPrototypes(relinkablePrototypes); } void deleteTypesForImportId(ImportId importId) { std::vector aliasPropertyDeclarations; std::vector relinkablePropertyDeclarations; + std::vector relinkablePrototypes; auto callback = [&](long long typeId) { - deleteType(TypeId{typeId}, aliasPropertyDeclarations, relinkablePropertyDeclarations); + deleteType(TypeId{typeId}, + aliasPropertyDeclarations, + relinkablePropertyDeclarations, + relinkablePrototypes); return Sqlite::CallbackControl::Continue; }; @@ -1037,7 +1094,7 @@ private: { if (Utils::visit([](auto &&typeName) -> bool { return typeName.name.isEmpty(); }, type.prototype)) { - updatePrototypeStatement.write(&type.typeId, Sqlite::NullValue{}); + updatePrototypeStatement.write(&type.typeId, Sqlite::NullValue{}, Sqlite::NullValue{}); } else { auto [prototypeId, prototypeTypeNameId] = fetchTypeIdByNameUngarded(type.prototype, type.sourceId); @@ -1045,7 +1102,7 @@ private: if (!prototypeId) throw TypeNameDoesNotExists{}; - updatePrototypeStatement.write(&type.typeId, &prototypeId); + updatePrototypeStatement.write(&type.typeId, &prototypeId, &prototypeTypeNameId); } } @@ -1307,6 +1364,7 @@ private: typesTable, Sqlite::ForeignKeyAction::NoAction, Sqlite::ForeignKeyAction::Restrict); + typesTable.addColumn("prototypeNameId"); typesTable.addUniqueIndex({importIdColumn, typesNameColumn}); @@ -1460,8 +1518,8 @@ public: "CONFLICT DO UPDATE SET prototypeId=excluded.prototypeId, " "accessSemantics=excluded.accessSemantics, sourceId=excluded.sourceId RETURNING typeId", database}; - WriteStatement updatePrototypeStatement{"UPDATE types SET prototypeId=?2 WHERE typeId=?1", - database}; + WriteStatement updatePrototypeStatement{ + "UPDATE types SET prototypeId=?2, prototypeNameId=?3 WHERE typeId=?1", database}; mutable ReadStatement<1> selectTypeIdByExportedNameStatement{ "SELECT typeId FROM typeNames WHERE name=?1 AND kind=1", database}; mutable ReadStatement<1> selectPrototypeIdStatement{ @@ -1753,6 +1811,13 @@ public: ReadStatement<1> selectPropertyDeclarationIdStatement{ "SELECT propertyDeclarationId FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; + ReadWriteStatement<3> updatePrototypeToNullStatement{ + "UPDATE types SET prototypeId=NULL WHERE prototypeId=?1 RETURNING " + "typeId, prototypeNameId, sourceId", + database}; + ReadStatement<1> selectTypeIdStatement{"SELECT typeId FROM types WHERE typeId=?", database}; + WriteStatement updateTypePrototypeStatement{ + "UPDATE types SET prototypeId=?2, prototypeNameId=?3 WHERE typeId=?1", database}; }; } // namespace QmlDesigner diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 3ec95c9ca2f..afbbca54f90 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -1034,7 +1034,7 @@ std::ostream &operator<<(std::ostream &out, const Type &type) { using Utils::operator<<; return out << "(import: " << type.importId << ", typename: \"" << type.typeName - << "\", prototype: \"" << type.prototype << "\", " << type.accessSemantics + << "\", prototype: " << type.prototype << ", " << type.accessSemantics << ", source: " << type.sourceId << ", exports: " << type.exportedTypes << ", properties: " << type.propertyDeclarations << ", functions: " << type.functionDeclarations diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index daeb2688b71..ddb3e2e2532 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -1191,7 +1191,7 @@ TEST_F(ProjectStorageSlowTest, BreakingPrototypeChainByDeletingBaseComponentThro types.pop_back(); ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1, sourceId2}), - Sqlite::ConstraintPreventsModification); + QmlDesigner::TypeNameDoesNotExists); } TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddPropertyDeclarations) @@ -3005,4 +3005,69 @@ TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyTypeDoesNotExists) ASSERT_THROW(storage.synchronizeTypes({}, {sourceId1}), QmlDesigner::TypeNameDoesNotExists); } + +TEST_F(ProjectStorageSlowTest, ChangePrototypeTypeName) +{ + Storage::Types types{createTypes()}; + types[0].propertyDeclarations[0].typeName = Storage::ExportedType{"Object"}; + types[0].prototype = Storage::ExportedType{"Object"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types[1].typeName = "QObject3"; + + storage.synchronizeTypes({types[1]}, {sourceId2}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(IsStorageType(importId2, + "QQuickItem", + Storage::NativeType{"QObject3"}, + TypeAccessSemantics::Reference, + sourceId1))); +} + +TEST_F(ProjectStorageSlowTest, ChangePrototypeTypeImportId) +{ + Storage::Types types{createTypes()}; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types[1].importId = importId2; + + storage.synchronizeTypes({types[1]}, {sourceId2}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(IsStorageType(importId2, + "QQuickItem", + Storage::NativeType{"QObject"}, + TypeAccessSemantics::Reference, + sourceId1))); +} + +TEST_F(ProjectStorageSlowTest, ChangePrototypeTypeNameAndImportId) +{ + Storage::Types types{createTypes()}; + types[0].propertyDeclarations[0].typeName = Storage::ExportedType{"Object"}; + types[0].prototype = Storage::ExportedType{"Object"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types[1].importId = importId2; + types[1].typeName = "QObject3"; + + storage.synchronizeTypes({types[1]}, {sourceId2}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(IsStorageType(importId2, + "QQuickItem", + Storage::NativeType{"QObject3"}, + TypeAccessSemantics::Reference, + sourceId1))); +} + +TEST_F(ProjectStorageSlowTest, ChangePrototypeTypeNameThrowsForWrongNativePrototupeTypeName) +{ + Storage::Types types{createTypes()}; + types[0].propertyDeclarations[0].typeName = Storage::ExportedType{"Object"}; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types[1].typeName = "QObject3"; + + ASSERT_THROW(storage.synchronizeTypes({types[1]}, {sourceId2}), + QmlDesigner::TypeNameDoesNotExists); +} + } // namespace