QmlDesigner: Handle more corner cases for alias relinking

Synchronization should always throw an exception if it gets in an
inconsitent state.

Task-number: QDS-4551
Change-Id: I8c55198115aa79b676a13fe0cd7ab225fb6723d5
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Marco Bubke
2021-07-01 10:42:38 +02:00
parent f08bd9df91
commit 32fd70ce3c
2 changed files with 188 additions and 9 deletions

View File

@@ -550,6 +550,16 @@ private:
auto [typeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(alias.aliasTypeName,
alias.sourceId);
if (!typeId) {
auto hasPropertyDeclaration = selectPropertyDeclarationIdStatement
.template optionalValue<PropertyDeclarationId>(
&alias.propertyDeclarationId);
if (hasPropertyDeclaration)
throw TypeNameDoesNotExists{};
continue;
}
auto [propertyTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeIdAndNameUngarded(
typeId, alias.aliasPropertyName);
@@ -1025,7 +1035,8 @@ private:
void syncPrototypes(Storage::Type &type)
{
if (Utils::visit([](auto &&type) -> bool { return type.name.isEmpty(); }, type.prototype)) {
if (Utils::visit([](auto &&typeName) -> bool { return typeName.name.isEmpty(); },
type.prototype)) {
updatePrototypeStatement.write(&type.typeId, Sqlite::NullValue{});
} else {
auto [prototypeId, prototypeTypeNameId] = fetchTypeIdByNameUngarded(type.prototype,
@@ -1482,9 +1493,10 @@ public:
" FROM propertyDeclarations JOIN typeSelection USING(typeId) "
" WHERE name=?2 ORDER BY level LIMIT 1",
database};
WriteStatement upsertTypeNamesStatement{"INSERT INTO typeNames(importId, name, typeId, kind) "
"VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING",
database};
WriteStatement upsertTypeNamesStatement{
"INSERT INTO typeNames(importId, name, typeId, kind) VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO "
"UPDATE SET typeId=excluded.typeId",
database};
mutable ReadStatement<1> selectPrototypeIdsStatement{
"WITH RECURSIVE "
" typeSelection(typeId, level) AS ("
@@ -1721,7 +1733,8 @@ public:
"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",
"alias.propertyTypeId=?1 OR alias.propertyTypeNameId IN (SELECT typeNameId FROM typeNames "
"WHERE typeId=?1)",
database};
ReadWriteStatement<2> updatesPropertyDeclarationPropertyTypeToNullStatement{
"UPDATE propertyDeclarations SET propertyTypeId=NULL WHERE propertyTypeId=?1 AND "

View File

@@ -531,7 +531,7 @@ protected:
types.push_back(Storage::Type{importId2,
"QAliasItem",
Storage::NativeType{"QQuickItem"},
Storage::ExportedType{"Item"},
TypeAccessSemantics::Reference,
sourceId3,
importIds,
@@ -541,9 +541,48 @@ protected:
Storage::NativeType{"QObject"},
Storage::PropertyDeclarationTraits::IsList});
types.back().aliasDeclarations.push_back(
Storage::AliasPropertyDeclaration{"items", Storage::NativeType{"QQuickItem"}, "children"});
Storage::AliasPropertyDeclaration{"items", Storage::ExportedType{"Item"}, "children"});
types.back().aliasDeclarations.push_back(
Storage::AliasPropertyDeclaration{"objects", Storage::NativeType{"QQuickItem"}, "objects"});
Storage::AliasPropertyDeclaration{"objects", Storage::ExportedType{"Item"}, "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;
}
auto createTypesWithAliases2()
{
auto types = createTypes();
types[1].propertyDeclarations.push_back(
Storage::PropertyDeclaration{"objects",
Storage::NativeType{"QObject"},
Storage::PropertyDeclarationTraits::IsList});
types.push_back(Storage::Type{importId2,
"QAliasItem",
Storage::NativeType{"QObject"},
TypeAccessSemantics::Reference,
sourceId3,
importIds,
{Storage::ExportedType{"AliasItem"}}});
types.back().propertyDeclarations.push_back(
Storage::PropertyDeclaration{"data",
Storage::NativeType{"QObject"},
Storage::PropertyDeclarationTraits::IsList});
types.back().aliasDeclarations.push_back(
Storage::AliasPropertyDeclaration{"objects", Storage::ExportedType{"Item"}, "objects"});
types.push_back(
Storage::Type{importId3,
@@ -1119,6 +1158,32 @@ TEST_F(ProjectStorageSlowTest, DontDeleteTypeIfSourceIdIsNotSynchronized)
UnorderedElementsAre(IsExportedType("Item"))))));
}
TEST_F(ProjectStorageSlowTest, UpdateExportedTypesIfTypeNameChanges)
{
Storage::Types types{createTypes()};
storage.synchronizeTypes(types, {sourceId1, sourceId2});
types[0].typeName = "QQuickItem2";
storage.synchronizeTypes({types[0]}, {sourceId1});
ASSERT_THAT(storage.fetchTypes(),
UnorderedElementsAre(AllOf(IsStorageType(importId1,
"QObject",
Storage::NativeType{},
TypeAccessSemantics::Reference,
sourceId2),
Field(&Storage::Type::exportedTypes,
UnorderedElementsAre(IsExportedType("Object"),
IsExportedType("Obj")))),
AllOf(IsStorageType(importId2,
"QQuickItem2",
Storage::NativeType{"QObject"},
TypeAccessSemantics::Reference,
sourceId1),
Field(&Storage::Type::exportedTypes,
UnorderedElementsAre(IsExportedType("Item"))))));
}
TEST_F(ProjectStorageSlowTest, BreakingPrototypeChainByDeletingBaseComponentThrows)
{
Storage::Types types{createTypes()};
@@ -2676,7 +2741,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeWithAliasTargetProperty
types[2].aliasDeclarations[1].aliasTypeName = Storage::ExportedType{"Object2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), Sqlite::ConstraintPreventsModification);
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), QmlDesigner::TypeNameDoesNotExists);
}
TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeAndAliasPropertyDeclaration)
@@ -2839,4 +2904,105 @@ TEST_F(ProjectStorageSlowTest, RelinkAliasProperty)
Storage::PropertyDeclarationTraits::IsList))))));
}
TEST_F(ProjectStorageSlowTest, RelinkAliasPropertyReactToTypeNameChange)
{
Storage::Types types{createTypesWithAliases2()};
types[2].aliasDeclarations.push_back(
Storage::AliasPropertyDeclaration{"items", Storage::ExportedType{"Item"}, "children"});
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types[0].typeName = "QQuickItem2";
storage.synchronizeTypes({types[0]}, {sourceId1});
ASSERT_THAT(storage.fetchTypes(),
Contains(AllOf(
IsStorageType(importId2,
"QAliasItem",
Storage::NativeType{"QObject"},
TypeAccessSemantics::Reference,
sourceId3),
Field(&Storage::Type::propertyDeclarations,
UnorderedElementsAre(
IsPropertyDeclaration("items",
Storage::NativeType{"QQuickItem2"},
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, DoNotRelinkAliasPropertyForDeletedType)
{
Storage::Types types{createTypesWithAliases()};
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types.erase(std::next(types.begin(), 2));
types[2].importId = importId2;
storage.synchronizeTypes({types[2]}, {sourceId3, sourceId4});
ASSERT_THAT(storage.fetchTypes(),
Not(Contains(AllOf(IsStorageType(importId2,
"QAliasItem",
Storage::NativeType{"QQuickItem"},
TypeAccessSemantics::Reference,
sourceId3)))));
}
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyForDeletedTypeAndPropertyType)
{
Storage::Types types{createTypesWithAliases()};
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types.pop_back();
types.pop_back();
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject"};
storage.synchronizeTypes({types[1]}, {sourceId2, sourceId3, sourceId4});
ASSERT_THAT(storage.fetchTypes(), SizeIs(2));
}
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyForDeletedTypeAndPropertyTypeNameChange)
{
Storage::Types types{createTypesWithAliases()};
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types.erase(std::next(types.begin(), 2));
types[2].importId = importId2;
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject"};
storage.synchronizeTypes({types[2]}, {sourceId3, sourceId4});
ASSERT_THAT(storage.fetchTypes(),
Not(Contains(AllOf(IsStorageType(importId2,
"QAliasItem",
Storage::NativeType{"QQuickItem"},
TypeAccessSemantics::Reference,
sourceId3)))));
}
TEST_F(ProjectStorageSlowTest, DoNotRelinkPropertyTypeDoesNotExists)
{
Storage::Types types{createTypesWithAliases()};
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types.pop_back();
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), QmlDesigner::TypeNameDoesNotExists);
}
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyTypeDoesNotExists)
{
Storage::Types types{createTypesWithAliases2()};
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
types.erase(types.begin());
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId1}), QmlDesigner::TypeNameDoesNotExists);
}
} // namespace