diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 3710021ff53..9a4bb0a2a2f 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -36,13 +36,6 @@ class DatabaseBackend; enum class Type : char { Invalid, Integer, Float, Text, Blob, Null }; -template -constexpr static std::underlying_type_t to_underlying(Enumeration enumeration) noexcept -{ - static_assert(std::is_enum_v, "to_underlying expect an enumeration"); - return static_cast>(enumeration); -} - class SQLITE_EXPORT BaseStatement { public: @@ -87,7 +80,7 @@ public: template> void bind(int index, Type id) { - if (id) + if (!id.isNull()) bind(index, id.internalId()); else bindNull(index); diff --git a/src/libs/sqlite/sqliteids.h b/src/libs/sqlite/sqliteids.h index 1ffd546d9f2..460cd91415a 100644 --- a/src/libs/sqlite/sqliteids.h +++ b/src/libs/sqlite/sqliteids.h @@ -11,6 +11,13 @@ namespace Sqlite { +template +static constexpr std::underlying_type_t to_underlying(Enumeration enumeration) noexcept +{ + static_assert(std::is_enum_v, "to_underlying expect an enumeration"); + return static_cast>(enumeration); +} + template class BasicId { @@ -27,7 +34,15 @@ public: return id; } - constexpr friend bool compareInvalidAreTrue(BasicId first, BasicId second) + template + static constexpr BasicId createSpecialState(Enumeration specialState) + { + BasicId id; + id.id = ::Sqlite::to_underlying(specialState); + return id; + } + + friend constexpr bool compareInvalidAreTrue(BasicId first, BasicId second) { return first.id == second.id; } @@ -57,6 +72,14 @@ public: constexpr bool isValid() const { return id > 0; } + constexpr bool isNull() const { return id == 0; } + + template + constexpr bool hasSpecialState(Enumeration specialState) const + { + return id == ::Sqlite::to_underlying(specialState); + } + explicit operator bool() const { return isValid(); } explicit operator std::size_t() const { return static_cast(id); } @@ -68,13 +91,13 @@ public: template friend void convertToString(String &string, BasicId id) { - if (id.isValid()) - NanotraceHR::convertToString(string, id.internalId()); + if (id.isNull()) + NanotraceHR::convertToString(string, "invalid null"); else - NanotraceHR::convertToString(string, "invalid"); + NanotraceHR::convertToString(string, id.internalId()); } -private: +protected: InternalIntegerType id = 0; }; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp index 8855c3251ca..2f56d569087 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp @@ -7,6 +7,25 @@ namespace QmlDesigner { +enum class SpecialIdState { Unresolved = -1 }; + +constexpr TypeId unresolvedTypeId = TypeId::createSpecialState(SpecialIdState::Unresolved); + +class UnresolvedTypeId : public TypeId +{ +public: + constexpr UnresolvedTypeId() + : TypeId{TypeId::createSpecialState(SpecialIdState::Unresolved)} + {} + + static constexpr UnresolvedTypeId create(DatabaseType idNumber) + { + UnresolvedTypeId id; + id.id = idNumber; + return id; + } +}; + struct ProjectStorage::Statements { Statements(Sqlite::Database &database) @@ -17,9 +36,13 @@ struct ProjectStorage::Statements Sqlite::ReadWriteStatement<1, 2> insertTypeStatement{ "INSERT OR IGNORE INTO types(sourceId, name) VALUES(?1, ?2) RETURNING typeId", database}; Sqlite::WriteStatement<5> updatePrototypeAndExtensionStatement{ - "UPDATE types SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 " - "WHERE typeId=?1 AND (prototypeId IS NOT ?2 OR extensionId IS NOT ?3 AND prototypeId " - "IS NOT ?4 OR extensionNameId IS NOT ?5)", + "UPDATE types " + "SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 " + "WHERE typeId=?1 AND ( " + " prototypeId IS NOT ?2 " + " OR extensionId IS NOT ?3 " + " OR prototypeId IS NOT ?4 " + " OR extensionNameId IS NOT ?5)", database}; mutable Sqlite::ReadStatement<1, 1> selectTypeIdByExportedNameStatement{ "SELECT typeId FROM exportedTypeNames WHERE name=?1", database}; @@ -357,13 +380,51 @@ struct ProjectStorage::Statements "SELECT name FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; Sqlite::WriteStatement<2> updatePropertyDeclarationTypeStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2 WHERE propertyDeclarationId=?1", database}; - Sqlite::ReadWriteStatement<2, 1> updatePrototypeIdToNullStatement{ - "UPDATE types SET prototypeId=NULL WHERE prototypeId=?1 RETURNING " - "typeId, prototypeNameId", + Sqlite::ReadWriteStatement<2, 2> updatePrototypeIdToTypeIdStatement{ + "UPDATE types " + "SET prototypeId=?2 " + "WHERE prototypeId=?1 " + "RETURNING typeId, prototypeNameId", database}; - Sqlite::ReadWriteStatement<2, 1> updateExtensionIdToNullStatement{ - "UPDATE types SET extensionId=NULL WHERE extensionId=?1 RETURNING " - "typeId, extensionNameId", + Sqlite::ReadWriteStatement<2, 2> updateExtensionIdToTypeIdStatement{ + "UPDATE types " + "SET extensionId=?2 " + "WHERE extensionId=?1 " + "RETURNING typeId, extensionNameId", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement{ + "SELECT typeId, prototypeNameId " + "FROM types " + "WHERE prototypeNameId IN ( " + " SELECT importedTypeNameId " + " FROM " + " importedTypeNames WHERE name=?1) " + " AND prototypeId=?2", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement{ + "SELECT typeId , prototypeNameId " + "FROM types " + "WHERE prototypeId=?1 AND sourceId=?2", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement{ + "SELECT typeId, extensionNameId " + "FROM types " + "WHERE extensionId=?1 AND sourceId=?2", + database}; + Sqlite::ReadWriteStatement<3, 3> updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement{ + "UPDATE types " + "SET prototypeId=?2, extensionId=?3 " + "WHERE sourceId=?1 " + "RETURNING typeId, prototypeNameId, extensionNameId", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdForExtensionIdAndTypeNameStatement{ + "SELECT typeId , prototypeNameId " + "FROM types " + "WHERE extensionNameId IN ( " + " SELECT importedTypeNameId " + " FROM importedTypeNames " + " WHERE name=?1) " + " AND extensionId=?2", database}; Sqlite::WriteStatement<2> updateTypePrototypeStatement{ "UPDATE types SET prototypeId=?2 WHERE typeId=?1", database}; @@ -619,6 +680,8 @@ struct ProjectStorage::Statements "UPDATE types SET defaultPropertyId=NULL WHERE defaultPropertyId=?1", database}; mutable Sqlite::ReadStatement<3, 1> selectInfoTypeByTypeIdStatement{ "SELECT sourceId, traits, annotationTraits FROM types WHERE typeId=?", database}; + mutable Sqlite::ReadStatement<1, 1> selectSourceIdByTypeIdStatement{ + "SELECT sourceId FROM types WHERE typeId=?", database}; mutable Sqlite::ReadStatement<1, 1> selectPrototypeAnnotationTraitsByTypeIdStatement{ "SELECT annotationTraits " "FROM types " @@ -801,23 +864,23 @@ public: auto &sourceIdColumn = typesTable.addColumn("sourceId", Sqlite::StrictColumnType::Integer); auto &typesNameColumn = typesTable.addColumn("name", Sqlite::StrictColumnType::Text); typesTable.addColumn("traits", Sqlite::StrictColumnType::Integer); - auto &prototypeIdColumn = typesTable.addForeignKeyColumn("prototypeId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); - typesTable.addColumn("prototypeNameId", Sqlite::StrictColumnType::Integer); - auto &extensionIdColumn = typesTable.addForeignKeyColumn("extensionId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); - typesTable.addColumn("extensionNameId", Sqlite::StrictColumnType::Integer); + auto &prototypeIdColumn = typesTable.addColumn("prototypeId", + Sqlite::StrictColumnType::Integer); + auto &prototypeNameIdColumn = typesTable.addColumn("prototypeNameId", + Sqlite::StrictColumnType::Integer); + auto &extensionIdColumn = typesTable.addColumn("extensionId", + Sqlite::StrictColumnType::Integer); + auto &extensionNameIdColumn = typesTable.addColumn("extensionNameId", + Sqlite::StrictColumnType::Integer); auto &defaultPropertyIdColumn = typesTable.addColumn("defaultPropertyId", Sqlite::StrictColumnType::Integer); typesTable.addColumn("annotationTraits", Sqlite::StrictColumnType::Integer); typesTable.addUniqueIndex({sourceIdColumn, typesNameColumn}); typesTable.addIndex({defaultPropertyIdColumn}); - typesTable.addIndex({prototypeIdColumn}); - typesTable.addIndex({extensionIdColumn}); + typesTable.addIndex({prototypeIdColumn, sourceIdColumn}); + typesTable.addIndex({extensionIdColumn, sourceIdColumn}); + typesTable.addIndex({prototypeNameIdColumn}); + typesTable.addIndex({extensionNameIdColumn}); typesTable.initialize(database); @@ -1131,7 +1194,7 @@ ProjectStorage::ProjectStorage(Database &database, ProjectStorageErrorNotifierInterface &errorNotifier, bool isInitialized) : database{database} - , errorNotifier{errorNotifier} + , errorNotifier{&errorNotifier} , exclusiveTransaction{database} , initializer{std::make_unique(database, isInitialized)} , moduleCache{ModuleStorageAdapter{*this}} @@ -1175,7 +1238,9 @@ void ProjectStorage::synchronize(Storage::Synchronization::SynchronizationPackag package.moduleDependencies, package.updatedModuleDependencySourceIds, package.moduleExportedImports, - package.updatedModuleIds); + package.updatedModuleIds, + relinkablePrototypes, + relinkableExtensions); synchronizeTypes(package.types, updatedTypeIds, insertedAliasPropertyDeclarations, @@ -1223,7 +1288,24 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports imports, Source keyValue("source id", sourceId)}; Sqlite::withImmediateTransaction(database, [&] { - synchronizeDocumentImports(imports, {sourceId}, Storage::Synchronization::ImportKind::Import); + AliasPropertyDeclarations relinkableAliasPropertyDeclarations; + PropertyDeclarations relinkablePropertyDeclarations; + Prototypes relinkablePrototypes; + Prototypes relinkableExtensions; + TypeIds deletedTypeIds; + + synchronizeDocumentImports(imports, + {sourceId}, + Storage::Synchronization::ImportKind::Import, + Relink::Yes, + relinkablePrototypes, + relinkableExtensions); + + relink(relinkableAliasPropertyDeclarations, + relinkablePropertyDeclarations, + relinkablePrototypes, + relinkableExtensions, + deletedTypeIds); }); } @@ -2642,19 +2724,29 @@ void ProjectStorage::synchronizeImports(Storage::Imports &imports, Storage::Imports &moduleDependencies, const SourceIds &updatedModuleDependencySourceIds, Storage::Synchronization::ModuleExportedImports &moduleExportedImports, - const ModuleIds &updatedModuleIds) + const ModuleIds &updatedModuleIds, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { NanotraceHR::Tracer tracer{"synchronize imports"_t, projectStorageCategory()}; synchromizeModuleExportedImports(moduleExportedImports, updatedModuleIds); NanotraceHR::Tracer importTracer{"synchronize qml document imports"_t, projectStorageCategory()}; - synchronizeDocumentImports(imports, updatedSourceIds, Storage::Synchronization::ImportKind::Import); + synchronizeDocumentImports(imports, + updatedSourceIds, + Storage::Synchronization::ImportKind::Import, + Relink::No, + relinkablePrototypes, + relinkableExtensions); importTracer.end(); NanotraceHR::Tracer moduleDependenciesTracer{"synchronize module depdencies"_t, projectStorageCategory()}; synchronizeDocumentImports(moduleDependencies, updatedModuleDependencySourceIds, - Storage::Synchronization::ImportKind::ModuleDependency); + Storage::Synchronization::ImportKind::ModuleDependency, + Relink::Yes, + relinkablePrototypes, + relinkableExtensions); moduleDependenciesTracer.end(); } @@ -2821,11 +2913,30 @@ void ProjectStorage::handlePrototypes(TypeId prototypeId, Prototypes &relinkable keyValue("type id", prototypeId), keyValue("relinkable prototypes", relinkablePrototypes)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + }; + + s->updatePrototypeIdToTypeIdStatement.readCallback(callback, prototypeId, unresolvedTypeId); +} + +void ProjectStorage::handlePrototypesWithExportedTypeNameAndTypeId( + Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkablePrototypes) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle invalid prototypes"_t, + projectStorageCategory(), + keyValue("type id", exportedTypeName), + keyValue("relinkable prototypes", relinkablePrototypes)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { relinkablePrototypes.emplace_back(typeId, prototypeNameId); }; - s->updatePrototypeIdToNullStatement.readCallback(callback, prototypeId); + s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement.readCallback(callback, + exportedTypeName, + typeId); } void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions) @@ -2836,11 +2947,28 @@ void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkable keyValue("type id", extensionId), keyValue("relinkable extensions", relinkableExtensions)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->updateExtensionIdToTypeIdStatement.readCallback(callback, extensionId, unresolvedTypeId); +} + +void ProjectStorage::handleExtensionsWithExportedTypeNameAndTypeId( + Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle invalid extensions"_t, + projectStorageCategory(), + keyValue("type id", exportedTypeName), + keyValue("relinkable extensions", relinkableExtensions)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { relinkableExtensions.emplace_back(typeId, extensionNameId); }; - s->updateExtensionIdToNullStatement.readCallback(callback, extensionId); + s->selectTypeIdForExtensionIdAndTypeNameStatement.readCallback(callback, exportedTypeName, typeId); } void ProjectStorage::deleteType(TypeId typeId, @@ -2927,6 +3055,39 @@ void ProjectStorage::relinkPropertyDeclarations(PropertyDeclarations &relinkable TypeCompare{}); } +template +void ProjectStorage::relinkPrototypes(Prototypes &relinkablePrototypes, + const TypeIds &deletedTypeIds, + Callable updateStatement) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"relink prototypes"_t, + projectStorageCategory(), + keyValue("relinkable prototypes", relinkablePrototypes), + keyValue("deleted type ids", deletedTypeIds)}; + + std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end()); + relinkablePrototypes.erase(std::unique(relinkablePrototypes.begin(), relinkablePrototypes.end()), + relinkablePrototypes.end()); + + Utils::set_greedy_difference( + relinkablePrototypes.cbegin(), + relinkablePrototypes.cend(), + deletedTypeIds.begin(), + deletedTypeIds.end(), + [&](const Prototype &prototype) { + TypeId prototypeId = fetchTypeId(prototype.prototypeNameId); + + if (!prototypeId) + errorNotifier->typeNameCannotBeResolved(fetchImportedTypeName(prototype.prototypeNameId), + fetchTypeSourceId(prototype.typeId)); + + updateStatement(prototype.typeId, prototypeId); + checkForPrototypeChainCycle(prototype.typeId); + }, + TypeCompare{}); +} + void ProjectStorage::deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &updatedSourceIds, const TypeIds &typeIdsToBeDeleted, @@ -3141,6 +3302,9 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds, } catch (const Sqlite::ConstraintPreventsModification &) { throw QmlDesigner::ExportedTypeCannotBeInserted{type.name}; } + + handlePrototypesWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkablePrototypes); + handleExtensionsWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkableExtensions); }; auto update = [&](const Storage::Synchronization::ExportedTypeView &view, @@ -3176,6 +3340,7 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds, relinkableAliasPropertyDeclarations); handlePrototypes(view.typeId, relinkablePrototypes); handleExtensions(view.typeId, relinkableExtensions); + s->deleteExportedTypeNameStatement.write(view.exportedTypeNameId); }; @@ -3491,11 +3656,90 @@ void ProjectStorage::resetRemovedAliasPropertyDeclarationsToNull( PropertyCompare{}); } +void ProjectStorage::handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId, + TypeId prototypeId, + Prototypes &relinkablePrototypes) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("type id", prototypeId)}; + + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + }; + + s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement.readCallback(callback, + prototypeId, + sourceId); +} + +void ProjectStorage::handlePrototypesAndExtensionsWithSourceId(SourceId sourceId, + TypeId prototypeId, + TypeId extensionId, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("prototype id", prototypeId), + keyValue("extension id", extensionId)}; + + auto callback = + [&](TypeId typeId, ImportedTypeNameId prototypeNameId, ImportedTypeNameId extensionNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement.readCallback(callback, + sourceId, + prototypeId, + extensionId); +} + +void ProjectStorage::handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId, + TypeId extensionId, + Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("type id", extensionId)}; + + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement.readCallback(callback, + extensionId, + sourceId); +} + ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import, Storage::Synchronization::ImportKind importKind, ModuleId sourceModuleId, - ImportId parentImportId) + ImportId parentImportId, + Relink relink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { + if (relink == Relink::Yes) { + handlePrototypesWithSourceIdAndPrototypeId(import.sourceId, + unresolvedTypeId, + relinkablePrototypes); + handleExtensionsWithSourceIdAndExtensionId(import.sourceId, + unresolvedTypeId, + relinkableExtensions); + } + if (import.version.minor) { return s->insertDocumentImportWithVersionStatement.value(import.sourceId, import.moduleId, @@ -3522,7 +3766,10 @@ ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import, void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, - Storage::Synchronization::ImportKind importKind) + Storage::Synchronization::ImportKind importKind, + Relink relink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { std::sort(imports.begin(), imports.end(), [](auto &&first, auto &&second) { return std::tie(first.sourceId, first.moduleId, first.version) @@ -3559,7 +3806,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, keyValue("source id", import.sourceId), keyValue("module id", import.moduleId)}; - auto importId = insertDocumentImport(import, importKind, import.moduleId, ImportId{}); + auto importId = insertDocumentImport(import, + importKind, + import.moduleId, + ImportId{}, + relink, + relinkablePrototypes, + relinkableExtensions); auto callback = [&](ModuleId exportedModuleId, int majorVersion, int minorVersion) { Storage::Import additionImport{exportedModuleId, Storage::Version{majorVersion, minorVersion}, @@ -3579,7 +3832,10 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, auto indirectImportId = insertDocumentImport(additionImport, exportedImportKind, import.moduleId, - importId); + importId, + relink, + relinkablePrototypes, + relinkableExtensions); tracer.end(keyValue("import id", indirectImportId)); }; @@ -3606,6 +3862,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, s->deleteDocumentImportStatement.write(view.importId); s->deleteDocumentImportsWithParentImportIdStatement.write(view.sourceId, view.importId); + if (relink == Relink::Yes) { + handlePrototypesAndExtensionsWithSourceId(view.sourceId, + unresolvedTypeId, + unresolvedTypeId, + relinkablePrototypes, + relinkableExtensions); + } }; Sqlite::insertUpdateDelete(range, imports, compareKey, insert, update, remove); @@ -4156,25 +4419,29 @@ void ProjectStorage::checkForAliasChainCycle(PropertyDeclarationId propertyDecla } std::pair ProjectStorage::fetchImportedTypeNameIdAndTypeId( - const Storage::Synchronization::ImportedTypeName &typeName, SourceId sourceId) + const Storage::Synchronization::ImportedTypeName &importedTypeName, SourceId sourceId) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"fetch imported type name id and type id"_t, projectStorageCategory(), - keyValue("imported type name", typeName), + keyValue("imported type name", importedTypeName), keyValue("source id", sourceId)}; TypeId typeId; ImportedTypeNameId typeNameId; - if (!std::visit([](auto &&typeName_) -> bool { return typeName_.name.isEmpty(); }, typeName)) { - typeNameId = fetchImportedTypeNameId(typeName, sourceId); + auto typeName = std::visit([](auto &&importedTypeName) { return importedTypeName.name; }, + importedTypeName); + if (!typeName.empty()) { + typeNameId = fetchImportedTypeNameId(importedTypeName, sourceId); typeId = fetchTypeId(typeNameId); tracer.end(keyValue("type id", typeId), keyValue("type name id", typeNameId)); - if (!typeId) - throw TypeNameDoesNotExists{fetchImportedTypeName(typeNameId), sourceId}; + if (!typeId) { + errorNotifier->typeNameCannotBeResolved(typeName, sourceId); + return {unresolvedTypeId, typeNameId}; + } } return {typeId, typeNameId}; @@ -4323,6 +4590,11 @@ Utils::SmallString ProjectStorage::fetchImportedTypeName(ImportedTypeNameId type return s->selectNameFromImportedTypeNamesStatement.value(typeNameId); } +SourceId ProjectStorage::fetchTypeSourceId(TypeId typeId) const +{ + return s->selectSourceIdByTypeIdStatement.value(typeId); +} + TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId, Storage::Synchronization::TypeNameKind kind) const { @@ -4334,9 +4606,10 @@ TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId, TypeId typeId; if (kind == Storage::Synchronization::TypeNameKind::Exported) { - typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value(typeNameId); + typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value(typeNameId); } else { - typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value(typeNameId); + typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value( + typeNameId); } tracer.end(keyValue("type id", typeId)); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index a5773448621..54d91015968 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -39,6 +39,8 @@ class ProjectStorage final : public ProjectStorageInterface using Database = Sqlite::Database; friend Storage::Info::CommonTypeCache; + enum class Relink { No, Yes }; + public: ProjectStorage(Database &database, ProjectStorageErrorNotifierInterface &errorNotifier, @@ -49,6 +51,11 @@ public: void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override; + void setErrorNotifier(ProjectStorageErrorNotifierInterface &errorNotifier) + { + this->errorNotifier = &errorNotifier; + } + void addObserver(ProjectStorageObserver *observer) override; void removeObserver(ProjectStorageObserver *observer) override; @@ -443,6 +450,11 @@ private: return first.typeId < second.typeId; } + friend bool operator==(Prototype first, Prototype second) + { + return first.typeId == second.typeId; + } + template friend void convertToString(String &string, const Prototype &prototype) { @@ -575,7 +587,9 @@ private: Storage::Imports &moduleDependencies, const SourceIds &updatedModuleDependencySourceIds, Storage::Synchronization::ModuleExportedImports &moduleExportedImports, - const ModuleIds &updatedModuleIds); + const ModuleIds &updatedModuleIds, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); void synchromizeModuleExportedImports( Storage::Synchronization::ModuleExportedImports &moduleExportedImports, @@ -593,9 +607,14 @@ private: PropertyDeclarations &relinkablePropertyDeclarations); void handlePrototypes(TypeId prototypeId, Prototypes &relinkablePrototypes); + void handlePrototypesWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName, + TypeId typeId, + Prototypes &relinkablePrototypes); void handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions); - + void handleExtensionsWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName, + TypeId typeId, + Prototypes &relinkableExtensions); void deleteType(TypeId typeId, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations, PropertyDeclarations &relinkablePropertyDeclarations, @@ -611,32 +630,7 @@ private: template void relinkPrototypes(Prototypes &relinkablePrototypes, const TypeIds &deletedTypeIds, - Callable updateStatement) - { - using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"relink prototypes"_t, - projectStorageCategory(), - keyValue("relinkable prototypes", relinkablePrototypes), - keyValue("deleted type ids", deletedTypeIds)}; - - std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end()); - - Utils::set_greedy_difference( - relinkablePrototypes.cbegin(), - relinkablePrototypes.cend(), - deletedTypeIds.begin(), - deletedTypeIds.end(), - [&](const Prototype &prototype) { - TypeId prototypeId = fetchTypeId(prototype.prototypeNameId); - - if (!prototypeId) - throw TypeNameDoesNotExists{fetchImportedTypeName(prototype.prototypeNameId)}; - - updateStatement(prototype.typeId, prototypeId); - checkForPrototypeChainCycle(prototype.typeId); - }, - TypeCompare{}); - } + Callable updateStatement); void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &updatedSourceIds, @@ -751,14 +745,32 @@ private: Storage::Synchronization::Types &types, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations); + void handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId, + TypeId prototypeId, + Prototypes &relinkablePrototypes); + void handlePrototypesAndExtensionsWithSourceId(SourceId sourceId, + TypeId prototypeId, + TypeId extensionId, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); + void handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId, + TypeId extensionId, + Prototypes &relinkableExtensions); + ImportId insertDocumentImport(const Storage::Import &import, Storage::Synchronization::ImportKind importKind, ModuleId sourceModuleId, - ImportId parentImportId); + ImportId parentImportId, + Relink forceRelink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); void synchronizeDocumentImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, - Storage::Synchronization::ImportKind importKind); + Storage::Synchronization::ImportKind importKind, + Relink forceRelink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); static Utils::PathString createJson(const Storage::Synchronization::ParameterDeclarations ¶meters); @@ -904,6 +916,7 @@ private: TypeId fetchTypeId(ImportedTypeNameId typeNameId) const; Utils::SmallString fetchImportedTypeName(ImportedTypeNameId typeNameId) const; + SourceId fetchTypeSourceId(TypeId typeId) const; TypeId fetchTypeId(ImportedTypeNameId typeNameId, Storage::Synchronization::TypeNameKind kind) const; @@ -970,7 +983,7 @@ private: public: Database &database; - ProjectStorageErrorNotifierInterface &errorNotifier; + ProjectStorageErrorNotifierInterface *errorNotifier = nullptr; // cannot be null Sqlite::ExclusiveNonThrowingDestructorTransaction exclusiveTransaction; std::unique_ptr initializer; mutable ModuleCache moduleCache{ModuleStorageAdapter{*this}}; diff --git a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h index 28443300cc1..730c70a66ab 100644 --- a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h +++ b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h @@ -9,6 +9,7 @@ class ProjectStorageErrorNotifierMock : public QmlDesigner::ProjectStorageErrorNotifierInterface { +public: MOCK_METHOD(void, typeNameCannotBeResolved, (Utils::SmallStringView typeName, QmlDesigner::SourceId souceId), diff --git a/tests/unit/tests/printers/gtest-creator-printing.cpp b/tests/unit/tests/printers/gtest-creator-printing.cpp index 0cbef7b1b7f..8ca65f4526e 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.cpp +++ b/tests/unit/tests/printers/gtest-creator-printing.cpp @@ -833,8 +833,9 @@ std::ostream &operator<<(std::ostream &out, const Type &type) { using std::operator<<; using Utils::operator<<; - return out << "( typename: \"" << type.typeName << "\", prototype: " << type.prototype << ", " - << type.prototypeId << ", " << type.traits << ", source: " << type.sourceId + return out << "( typename: \"" << type.typeName << "\", prototype: {\"" << type.prototype + << "\", " << type.prototypeId << "}, " << "\", extension: {\"" << type.extension + << "\", " << type.extensionId << "}, " << type.traits << ", source: " << type.sourceId << ", exports: " << type.exportedTypes << ", properties: " << type.propertyDeclarations << ", functions: " << type.functionDeclarations << ", signals: " << type.signalDeclarations << ", changeLevel: " << type.changeLevel diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index c59971fec1c..fc806d73a95 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -205,6 +205,23 @@ MATCHER_P4(IsInfoPropertyDeclaration, && propertyDeclaration.traits == traits; } +auto IsUnresolvedTypeId() +{ + return Property(&QmlDesigner::TypeId::internalId, -1); +} + +template +auto IsPrototypeId(const Matcher &matcher) +{ + return Field(&Storage::Synchronization::Type::prototypeId, matcher); +} + +template +auto IsExtensionId(const Matcher &matcher) +{ + return Field(&Storage::Synchronization::Type::extensionId, matcher); +} + class HasNameMatcher { public: @@ -286,6 +303,8 @@ protected: static void TearDownTestSuite() { staticData.reset(); } + ProjectStorage() { storage.setErrorNotifier(errorNotifierMock); } + ~ProjectStorage() { storage.resetForTestsOnly(); } template @@ -312,6 +331,32 @@ protected: storage.fetchSourceId(sourceContextId3, "bar"); } + auto createVerySimpleSynchronizationPackage() + { + SynchronizationPackage package; + + package.types.push_back(Storage::Synchronization::Type{ + "QQuickItem", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId1, + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, + Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}); + package.types.push_back(Storage::Synchronization::Type{ + "QObject", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object"}, + Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject"}}}); + + package.updatedSourceIds = {sourceId1, sourceId2}; + + return package; + } + auto createSimpleSynchronizationPackage() { SynchronizationPackage package; @@ -1142,7 +1187,7 @@ protected: inline static std::unique_ptr staticData; Sqlite::Database &database = staticData->database; QmlDesigner::ProjectStorage &storage = staticData->storage; - ProjectStorageErrorNotifierMock &errorNotifierMock = staticData->errorNotifierMock; + NiceMock errorNotifierMock; QmlDesigner::SourcePathCache sourcePathCache{storage}; QmlDesigner::SourcePathView path1{"/path1/to"}; QmlDesigner::SourcePathView path2{"/path2/to"}; @@ -1486,22 +1531,193 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_extension_ "QQuickItem")))))); } -TEST_F(ProjectStorage, synchronize_types_adds_new_types_throws_with_wrong_prototype_name) +TEST_F(ProjectStorage, + synchronize_types_adds_unknown_prototype_which_notifies_about_unresolved_type_name) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; - ASSERT_THROW(storage.synchronize(std::move(package)), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); } -TEST_F(ProjectStorage, synchronize_types_adds_new_types_throws_with_wrong_extension_name) +TEST_F(ProjectStorage, synchronize_types_adds_unknown_prototype_as_unresolved_type_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, synchronize_types_updates_unresolved_prototype_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_unresolved_prototype_indirectly_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_indirectly_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_indirectly_to_unresolved_after_exported_type_name_is_removed_notifies_type_name_cannot_be_resolved) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); +} + +TEST_F(ProjectStorage, synchronize_types_updates_unresolved_extension_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_extension_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_unresolved_extension_indirectly_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_invalid_extension_indirectly_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_extension_indirectly_to_unresolved_after_exported_type_name_is_removed_notifies_type_name_cannot_be_resolved) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); +} + +TEST_F(ProjectStorage, synchronize_types_adds_extension_which_notifies_about_unresolved_type_name) { auto package{createSimpleSynchronizationPackage()}; package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; - ASSERT_THROW(storage.synchronize(std::move(package)), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); } + TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_missing_module) { auto package{createSimpleSynchronizationPackage()}; @@ -1774,7 +1990,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); } -TEST_F(ProjectStorage, synchronize_types_throws_for_missing_prototype) +TEST_F(ProjectStorage, synchronize_types_notifies_cannot_resolve_for_missing_prototype) { auto package{createSimpleSynchronizationPackage()}; package.types = {Storage::Synchronization::Type{ @@ -1786,10 +2002,12 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_prototype) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, synchronize_types_throws_for_missing_extension) +TEST_F(ProjectStorage, synchronize_types_notifies_cannot_resolve_for_missing_extension) { auto package{createSimpleSynchronizationPackage()}; package.types = {Storage::Synchronization::Type{ @@ -1801,7 +2019,9 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_extension) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, synchronize_types_throws_for_invalid_module) @@ -3670,7 +3890,8 @@ TEST_F(ProjectStorage, change_extension_type_module_id) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) +TEST_F(ProjectStorage, + change_qualified_prototype_type_module_id_notifies_that_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ @@ -3678,12 +3899,12 @@ TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } -TEST_F(ProjectStorage, change_qualified_extension_type_module_id_throws) +TEST_F(ProjectStorage, change_qualified_extension_type_module_id_notifies_cannot_resolve) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); @@ -3692,9 +3913,9 @@ TEST_F(ProjectStorage, change_qualified_extension_type_module_id_throws) storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, change_qualified_prototype_type_module_id) @@ -3798,9 +4019,9 @@ TEST_F(ProjectStorage, change_prototype_type_name_throws_for_wrong_native_protot package.types[1].exportedTypes[2].name = "QObject3"; package.types[1].typeName = "QObject3"; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, change_extension_type_name_throws_for_wrong_native_extension_type_name) @@ -3813,9 +4034,9 @@ TEST_F(ProjectStorage, change_extension_type_name_throws_for_wrong_native_extens package.types[1].exportedTypes[2].name = "QObject3"; package.types[1].typeName = "QObject3"; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, throw_for_prototype_chain_cycles) @@ -4117,23 +4338,29 @@ TEST_F(ProjectStorage, qualified_extension) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_upper_down_the_module_chain_throws) +TEST_F(ProjectStorage, + qualified_prototype_upper_down_the_module_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_upper_down_the_module_chain_throws) +TEST_F(ProjectStorage, + qualified_extension_upper_down_the_module_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_prototype_upper_in_the_module_chain) @@ -4189,7 +4416,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) +TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ @@ -4205,10 +4432,12 @@ TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) +TEST_F(ProjectStorage, qualified_extension_with_wrong_version_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); @@ -4225,7 +4454,9 @@ TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_prototype_with_version) @@ -4338,23 +4569,29 @@ TEST_F(ProjectStorage, qualified_extension_with_version_in_the_proto_type_chain) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_with_version_down_the_proto_type_chain_throws) +TEST_F(ProjectStorage, + qualified_prototype_with_version_down_the_proto_type_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_with_version_down_the_proto_type_chain_throws) +TEST_F(ProjectStorage, + qualified_extension_with_version_down_the_proto_type_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_property_declaration_type_name) @@ -4659,7 +4896,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_qualified_im } TEST_F(ProjectStorage, - fetch_by_major_version_and_minor_version_for_imported_type_if_minor_version_is_not_exported_throws) + fetch_by_major_version_and_minor_version_for_imported_type_if_minor_version_is_not_exported_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4673,12 +4910,13 @@ TEST_F(ProjectStorage, Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, - fetch_by_major_version_and_minor_version_for_qualified_imported_type_if_minor_version_is_not_exported_throws) + fetch_by_major_version_and_minor_version_for_qualified_imported_type_if_minor_version_is_not_exported_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4691,11 +4929,12 @@ TEST_F(ProjectStorage, sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) +TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4709,11 +4948,13 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throws) +TEST_F(ProjectStorage, + fetch_low_minor_version_for_qualified_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4726,8 +4967,9 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throw sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, fetch_higher_minor_version_for_imported_type) @@ -4775,7 +5017,8 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_qualified_imported_type) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) +TEST_F(ProjectStorage, + fetch_different_major_version_for_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4789,11 +5032,13 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{3, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type_throws) +TEST_F(ProjectStorage, + fetch_different_major_version_for_qualified_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4806,8 +5051,9 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_imported_type) @@ -7248,6 +7494,46 @@ TEST_F(ProjectStorage, synchronize_document_imports_adds_import) ASSERT_TRUE(storage.importId(imports.back())); } +TEST_F(ProjectStorage, + synchronize_document_imports_removes_import_notifies_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronizeDocumentImports({}, sourceId1); +} + +TEST_F(ProjectStorage, synchronize_document_imports_removes_import_which_makes_prototype_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + + storage.synchronizeDocumentImports({}, sourceId1); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, synchronize_document_imports_adds_import_which_makes_prototype_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + Storage::Imports imports; + imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + + storage.synchronizeDocumentImports(imports, sourceId1); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + TEST_F(ProjectStorage, get_exported_type_names) { auto package{createSimpleSynchronizationPackage()}; @@ -7996,4 +8282,105 @@ TEST_F(ProjectStorage, get_no_hair_ids_for_invalid_type_id) ASSERT_THAT(heirIds, IsEmpty()); } + +TEST_F(ProjectStorage, + removed_document_import_notifies_for_prototypes_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); +} + +TEST_F(ProjectStorage, + removed_document_import_notifies_for_extensions_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); +} + +TEST_F(ProjectStorage, removed_document_import_changes_prototype_to_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + Field(&Storage::Synchronization::Type::prototypeId, IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, removed_document_import_changes_extension_to_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + Field(&Storage::Synchronization::Type::extensionId, IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, added_document_import_fixes_unresolved_prototype) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, added_document_import_fixes_unresolved_extension) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + } // namespace diff --git a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp index f533c651e96..24b20176eaa 100644 --- a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp +++ b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp @@ -269,6 +269,21 @@ TEST_F(SqliteStatement, bind_int_id) ASSERT_THAT(readStatement.fetchIntValue(0), 42); } +TEST_F(SqliteStatement, bind_special_state_id) +{ + enum class SpecialIdState { Unresolved = -1 }; + constexpr TestIntId unresolvedTypeId = TestIntId::createSpecialState(SpecialIdState::Unresolved); + SqliteTestStatement<0, 1> statement("INSERT INTO test VALUES ('id', 323, ?)", database); + + statement.bind(1, unresolvedTypeId); + statement.next(); + + SqliteTestStatement<1, 1> readStatement("SELECT value FROM test WHERE name='id'", database); + readStatement.next(); + ASSERT_THAT(readStatement.fetchType(0), Sqlite::Type::Integer); + ASSERT_THAT(readStatement.fetchIntValue(0), -1); +} + TEST_F(SqliteStatement, bind_invalid_long_long_id_to_null) { TestLongLongId id;