QmlDesigner: Synchronize type

A type should not removed but all it's fields should be reseted. This
has the advantage that if you remove a file and add it again later the
id is stable.

Change-Id: I3a1f51e14195cff2fc39681acdf7072d1a2e4616
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2021-05-31 18:13:42 +02:00
parent bc2af18a41
commit 029aecef1e
3 changed files with 98 additions and 31 deletions

View File

@@ -63,8 +63,12 @@ public:
TypeIds updatedTypeIds; TypeIds updatedTypeIds;
updatedTypeIds.reserve(types.size()); updatedTypeIds.reserve(types.size());
for (auto &&type : types) for (auto &&type : types) {
if (!type.sourceId)
throw TypeHasInvalidSourceId{};
updatedTypeIds.push_back(syncType(type)); updatedTypeIds.push_back(syncType(type));
}
deleteNotUpdatedTypes(updatedTypeIds, sourceIds); deleteNotUpdatedTypes(updatedTypeIds, sourceIds);
@@ -262,8 +266,17 @@ private:
return &sourceId; return &sourceId;
}); });
deleteNotUpdatedTypesInSourcesStatement.write(Utils::span(sourceIdValues), auto removedTypeIds = selectNotUpdatedTypesInSourcesStatement.template range<TypeId>(
Utils::span(updatedTypeIdValues)); 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) void upsertExportedType(Utils::SmallStringView qualifiedName, Storage::Version version, TypeId typeId)
@@ -689,7 +702,6 @@ private:
Sqlite::ForeignKeyAction::Restrict, Sqlite::ForeignKeyAction::Restrict,
Sqlite::ForeignKeyAction::Restrict, Sqlite::ForeignKeyAction::Restrict,
Sqlite::Enforment::Deferred); Sqlite::Enforment::Deferred);
table.addColumn("defaultProperty");
table.addUniqueIndex({nameColumn}); table.addUniqueIndex({nameColumn});
@@ -864,9 +876,21 @@ public:
"SELECT name, typeId, (SELECT name FROM types WHERE typeId=outerTypes.prototypeId)," "SELECT name, typeId, (SELECT name FROM types WHERE typeId=outerTypes.prototypeId),"
"accessSemantics, ifnull(sourceId, -1) FROM types AS outerTypes", "accessSemantics, ifnull(sourceId, -1) FROM types AS outerTypes",
database}; database};
WriteStatement deleteNotUpdatedTypesInSourcesStatement{ ReadStatement<1> selectNotUpdatedTypesInSourcesStatement{
"DELETE FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN carray(?2)) OR sourceId " "SELECT typeId FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN carray(?2))",
"IS NULL", 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}; database};
mutable ReadStatement<3> selectPropertyDeclarationsByTypeIdStatement{ mutable ReadStatement<3> selectPropertyDeclarationsByTypeIdStatement{
"SELECT name, (SELECT name FROM types WHERE typeId=propertyDeclarations.propertyTypeId)," "SELECT name, (SELECT name FROM types WHERE typeId=propertyDeclarations.propertyTypeId),"

View File

@@ -65,4 +65,10 @@ public:
} }
}; };
class TypeHasInvalidSourceId : std::exception
{
public:
const char *what() const noexcept override { return "The source id is invalid!"; }
};
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -82,6 +82,19 @@ MATCHER_P4(IsStorageType,
&& type.accessSemantics == accessSemantics && type.sourceId == sourceId; && 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, MATCHER_P2(IsExportedType,
qualifiedTypeName, qualifiedTypeName,
version, version,
@@ -415,16 +428,6 @@ TEST_F(ProjectStorage,
storage.fetchSourceContextId("/other/unknow/path"); 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) TEST_F(ProjectStorage, FetchTypeByTypeIdCalls)
{ {
InSequence s; InSequence s;
@@ -1271,8 +1274,9 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesInsertTypeIntoPrototypeChain)
IsExportedType("QtQuick.Item", Storage::Version{5, 15})))))); IsExportedType("QtQuick.Item", Storage::Version{5, 15}))))));
} }
TEST_F(ProjectStorageSlowTest, SynchronizeTypesThrowsForMissingPrototype) TEST_F(ProjectStorageSlowTest, SynchronizeTypesDontThrowsForMissingPrototype)
{ {
sourceId1 = sourcePathCache.sourceId(path1);
Storage::Types types{ Storage::Types types{
Storage::Type{"QQuickItem", Storage::Type{"QQuickItem",
"QObject", "QObject",
@@ -1280,19 +1284,10 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesThrowsForMissingPrototype)
sourceId1, sourceId1,
{Storage::ExportedType{"QtQuick.Item", Storage::Version{5, 15}}}}}; {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) TEST_F(ProjectStorageSlowTest, TypeWithInvalidSourceIdThrows)
{
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)
{ {
Storage::Types types{ Storage::Types types{
Storage::Type{"QQuickItem", Storage::Type{"QQuickItem",
@@ -1301,9 +1296,51 @@ TEST_F(ProjectStorageSlowTest, DontAddTypeWithInvalidSourceId)
SourceId{}, SourceId{},
{Storage::ExportedType{"QtQuick.Item", Storage::Version{5, 15}}}}}; {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) TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddPropertyDeclarations)