diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index e8edd79b1ba..22306e8c0db 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -63,8 +63,12 @@ public: TypeIds updatedTypeIds; updatedTypeIds.reserve(types.size()); - for (auto &&type : types) + for (auto &&type : types) { + if (!type.sourceId) + throw TypeHasInvalidSourceId{}; + updatedTypeIds.push_back(syncType(type)); + } deleteNotUpdatedTypes(updatedTypeIds, sourceIds); @@ -262,8 +266,17 @@ private: return &sourceId; }); - deleteNotUpdatedTypesInSourcesStatement.write(Utils::span(sourceIdValues), - Utils::span(updatedTypeIdValues)); + auto removedTypeIds = selectNotUpdatedTypesInSourcesStatement.template range( + Utils::span(sourceIdValues), Utils::span(updatedTypeIdValues)); + + for (TypeId typeId : removedTypeIds) { + resetTypeStatement.write(&typeId); + deleteExportTypesByTypeIdStatement.write(&typeId); + deleteEnumerationDeclarationByTypeIdStatement.write(&typeId); + deletePropertyDeclarationByTypeIdStatement.write(&typeId); + deleteFunctionDeclarationByTypeIdStatement.write(&typeId); + deleteSignalDeclarationByTypeIdStatement.write(&typeId); + } } void upsertExportedType(Utils::SmallStringView qualifiedName, Storage::Version version, TypeId typeId) @@ -689,7 +702,6 @@ private: Sqlite::ForeignKeyAction::Restrict, Sqlite::ForeignKeyAction::Restrict, Sqlite::Enforment::Deferred); - table.addColumn("defaultProperty"); table.addUniqueIndex({nameColumn}); @@ -864,9 +876,21 @@ public: "SELECT name, typeId, (SELECT name FROM types WHERE typeId=outerTypes.prototypeId)," "accessSemantics, ifnull(sourceId, -1) FROM types AS outerTypes", database}; - WriteStatement deleteNotUpdatedTypesInSourcesStatement{ - "DELETE FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN carray(?2)) OR sourceId " - "IS NULL", + 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 deleteEnumerationDeclarationByTypeIdStatement{ + "DELETE FROM enumerationDeclarations WHERE typeId=?", database}; + WriteStatement deletePropertyDeclarationByTypeIdStatement{ + "DELETE FROM propertyDeclarations WHERE typeId=?", database}; + WriteStatement deleteFunctionDeclarationByTypeIdStatement{ + "DELETE FROM functionDeclarations WHERE typeId=?", database}; + WriteStatement deleteSignalDeclarationByTypeIdStatement{ + "DELETE FROM signalDeclarations WHERE typeId=?", database}; + WriteStatement resetTypeStatement{ + "UPDATE types SET accessSemantics=NULL, sourceId=NULL, prototypeId=NULL WHERE typeId=?", database}; mutable ReadStatement<3> selectPropertyDeclarationsByTypeIdStatement{ "SELECT name, (SELECT name FROM types WHERE typeId=propertyDeclarations.propertyTypeId)," diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h index 3fb31f566a6..5204512c9e4 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h @@ -65,4 +65,10 @@ public: } }; +class TypeHasInvalidSourceId : std::exception +{ +public: + const char *what() const noexcept override { return "The source id is invalid!"; } +}; + } // namespace QmlDesigner diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index 2733a0e81cc..7549256b919 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -82,6 +82,19 @@ MATCHER_P4(IsStorageType, && type.accessSemantics == accessSemantics && type.sourceId == sourceId; } +MATCHER_P3(IsStorageTypeWithInvalidSourceId, + typeName, + prototype, + accessSemantics, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::Type{typeName, prototype, accessSemantics, SourceId{}})) +{ + const Storage::Type &type = arg; + + return type.typeName == typeName && type.prototype == prototype + && type.accessSemantics == accessSemantics && !type.sourceId.isValid(); +} + MATCHER_P2(IsExportedType, qualifiedTypeName, version, @@ -415,16 +428,6 @@ TEST_F(ProjectStorage, storage.fetchSourceContextId("/other/unknow/path"); } -TEST_F(ProjectStorage, SynchronizeTypesCalls) -{ - InSequence s; - - EXPECT_CALL(databaseMock, immediateBegin()); - EXPECT_CALL(databaseMock, commit()); - - storage.synchronizeTypes({}, {}); -} - TEST_F(ProjectStorage, FetchTypeByTypeIdCalls) { InSequence s; @@ -1271,8 +1274,9 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesInsertTypeIntoPrototypeChain) IsExportedType("QtQuick.Item", Storage::Version{5, 15})))))); } -TEST_F(ProjectStorageSlowTest, SynchronizeTypesThrowsForMissingPrototype) +TEST_F(ProjectStorageSlowTest, SynchronizeTypesDontThrowsForMissingPrototype) { + sourceId1 = sourcePathCache.sourceId(path1); Storage::Types types{ Storage::Type{"QQuickItem", "QObject", @@ -1280,19 +1284,10 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesThrowsForMissingPrototype) sourceId1, {Storage::ExportedType{"QtQuick.Item", Storage::Version{5, 15}}}}}; - ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1}), Sqlite::ConstraintPreventsModification); + ASSERT_NO_THROW(storage.synchronizeTypes(types, {sourceId1})); } -TEST_F(ProjectStorageSlowTest, SynchronizeTypesThrowsForWrongPrototypeChange) -{ - Storage::Types types{createTypes()}; - storage.synchronizeTypes(types, {sourceId1, sourceId2}); - types[0].prototype = "QQuickObject"; - - ASSERT_THROW(storage.synchronizeTypes(types, {sourceId1}), Sqlite::ConstraintPreventsModification); -} - -TEST_F(ProjectStorageSlowTest, DontAddTypeWithInvalidSourceId) +TEST_F(ProjectStorageSlowTest, TypeWithInvalidSourceIdThrows) { Storage::Types types{ Storage::Type{"QQuickItem", @@ -1301,9 +1296,51 @@ TEST_F(ProjectStorageSlowTest, DontAddTypeWithInvalidSourceId) SourceId{}, {Storage::ExportedType{"QtQuick.Item", Storage::Version{5, 15}}}}}; - storage.synchronizeTypes(types, {}); + ASSERT_THROW(storage.synchronizeTypes(types, {}), QmlDesigner::TypeHasInvalidSourceId); +} - ASSERT_THAT(storage.fetchTypes(), IsEmpty()); +TEST_F(ProjectStorageSlowTest, ResetsTypeIfTypeIsRemoved) +{ + Storage::Types types{createTypes()}; + types[1].enumerationDeclarations = types[0].enumerationDeclarations; + types[1].propertyDeclarations = types[0].propertyDeclarations; + types[1].functionDeclarations = types[0].functionDeclarations; + types[1].signalDeclarations = types[0].signalDeclarations; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types.pop_back(); + + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageTypeWithInvalidSourceId("QObject", "", TypeAccessSemantics::Invalid), + Field(&Storage::Type::exportedTypes, IsEmpty()), + Field(&Storage::Type::enumerationDeclarations, IsEmpty()), + Field(&Storage::Type::propertyDeclarations, IsEmpty()), + Field(&Storage::Type::functionDeclarations, IsEmpty()), + Field(&Storage::Type::signalDeclarations, IsEmpty())))); +} + +TEST_F(ProjectStorageSlowTest, DontResetsTypeIfSourceIdIsNotSynchronized) +{ + Storage::Types types{createTypes()}; + types[1].enumerationDeclarations = types[0].enumerationDeclarations; + types[1].propertyDeclarations = types[0].propertyDeclarations; + types[1].functionDeclarations = types[0].functionDeclarations; + types[1].signalDeclarations = types[0].signalDeclarations; + storage.synchronizeTypes(types, {sourceId1, sourceId2}); + types.pop_back(); + + storage.synchronizeTypes(types, {sourceId1}); + + ASSERT_THAT(storage.fetchTypes(), + Contains(AllOf(IsStorageType("QObject", "", TypeAccessSemantics::Reference, sourceId2), + Field(&Storage::Type::exportedTypes, Not(IsEmpty())), + Field(&Storage::Type::exportedTypes, Not(IsEmpty())), + Field(&Storage::Type::enumerationDeclarations, Not(IsEmpty())), + Field(&Storage::Type::propertyDeclarations, Not(IsEmpty())), + Field(&Storage::Type::functionDeclarations, Not(IsEmpty())), + Field(&Storage::Type::signalDeclarations, Not(IsEmpty()))))); } TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddPropertyDeclarations)