From a10575bfc367bb9b124d95940800b7e7dc66a234 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 09:03:44 -0500 Subject: [PATCH] fix #5897 - allow SQLite to drop columns, remove deprecated columns, and handle upgrade logic --- .../Databases/Sqlite/SqliteDatabase.cs | 5 +- ...020201_RemoveFolderFileDeletableColumns.cs | 19 +++--- .../03030201_AddFolderFileIsDeletedColumns.cs | 2 +- .../Tenant/05020401_RemoveLanguageName.cs | 7 ++- .../Tenant/06000101_AddLanguageName.cs | 2 +- .../Tenant/10000102_RemoveSiteTenantId.cs | 5 +- .../10000201_RemoveDeprecatedColumns.cs | 60 +++++++++++++++++++ .../Admin/Files/Manager/FileManager.cs | 3 +- Oqtane.Server/Repository/FileRepository.cs | 1 - Oqtane.Server/Repository/FolderRepository.cs | 1 - Oqtane.Shared/Models/File.cs | 19 +++--- Oqtane.Shared/Models/Folder.cs | 12 ++-- Oqtane.Shared/Models/Language.cs | 3 +- 13 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs diff --git a/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs b/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs index e8cd6fd3..7d1226da 100644 --- a/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs +++ b/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs @@ -31,12 +31,15 @@ namespace Oqtane.Database.Sqlite public override void DropColumn(MigrationBuilder builder, string name, string table) { - // not implemented as SQLite does not support dropping columns + // SQLite supports dropping columns starting with version 3.35.0 but EF Core does not implement it yet + // note that a column cannot be dropped if it has a UNIQUE constraint, is part of a PRIMARY KEY, is indexed, or is referenced by other parts of the schema + builder.Sql($"ALTER TABLE {table} DROP COLUMN {name};"); } public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index) { // not implemented as SQLite does not support altering columns + // note that column length does not need to be modified as SQLite uses a TEXT type which utilizes variable length strings } public override string ConcatenateSql(params string[] values) diff --git a/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs b/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs index f17cda4b..ec2fff10 100644 --- a/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs +++ b/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs @@ -16,15 +16,18 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); - folderEntityBuilder.DropColumn("DeletedBy"); - folderEntityBuilder.DropColumn("DeletedOn"); - folderEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name != "Sqlite") + { + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.DropColumn("DeletedBy"); + folderEntityBuilder.DropColumn("DeletedOn"); + folderEntityBuilder.DropColumn("IsDeleted"); - var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); - fileEntityBuilder.DropColumn("DeletedBy"); - fileEntityBuilder.DropColumn("DeletedOn"); - fileEntityBuilder.DropColumn("IsDeleted"); + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.DropColumn("DeletedBy"); + fileEntityBuilder.DropColumn("DeletedOn"); + fileEntityBuilder.DropColumn("IsDeleted"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs index ad954a7d..ae62f197 100644 --- a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs +++ b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs @@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - // IsDeleted columns were removed in 3.2.2 however SQLite does not support column removal so they had to be restored + // IsDeleted columns were removed in 3.2.2 however SQLite did not support column removal so they had to be restored if (ActiveDatabase.Name != "Sqlite") { var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); diff --git a/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs index 063b7027..b2f814d4 100644 --- a/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs +++ b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs @@ -16,8 +16,11 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); - languageEntityBuilder.DropColumn("Name"); + if (ActiveDatabase.Name != "Sqlite") + { + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.DropColumn("Name"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs index 76fedad6..69c48ddf 100644 --- a/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs +++ b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs @@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - // Name column was removed in 5.2.4 however SQLite does not support column removal so it had to be restored + // Name column was removed in 5.2.4 however SQLite did not support column removal so it had to be restored if (ActiveDatabase.Name != "Sqlite") { var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); diff --git a/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs b/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs index 3ac75cbe..117f6af4 100644 --- a/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs +++ b/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs @@ -18,7 +18,10 @@ namespace Oqtane.Migrations.Tenant { var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); siteEntityBuilder.DropIndex("IX_Site"); // TenantId, Name - siteEntityBuilder.DropColumn("TenantId"); + if (ActiveDatabase.Name != "Sqlite") + { + siteEntityBuilder.DropColumn("TenantId"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs b/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs new file mode 100644 index 00000000..c719f88c --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.10.00.02.01")] + public class RemoveDeprecatedColumns : MultiDatabaseMigration + { + public RemoveDeprecatedColumns(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + // Oqtane 10.0.2 includes support for column removal in SQLite, so we can now clean up deprecated columns + + // Folder columns were deprecated in Oqtane 3.2.2 + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following columns were not added back in 3.2.3 but they still exist in SQLite databases + folderEntityBuilder.DropColumn("DeletedBy"); + folderEntityBuilder.DropColumn("DeletedOn"); + } + + // File columns were deprecated in Oqtane 3.2.2 + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + // IsDeleted was added back in 3.2.3 for non-SQLLite databases + fileEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following columns were not added back in 3.2.3 but they still exist in SQLite databases + fileEntityBuilder.DropColumn("DeletedBy"); + fileEntityBuilder.DropColumn("DeletedOn"); + } + + // Language columns were deprecated in Oqtane 5.2.4 + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.DropColumn("Name"); + + // Site columns were deprecated in Oqtane 10.0.1 + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following column was removed for non-SQLite databases in 10.0.1 + siteEntityBuilder.DropColumn("TenantId"); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs index 43dbda33..dd684d59 100644 --- a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs +++ b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs @@ -34,7 +34,6 @@ namespace Oqtane.Modules.Admin.Files.Manager if (folder.ModifiedOn >= lastIndexedOn) { changed = true; - removed = folder.IsDeleted.Value; } var files = _fileRepository.GetFiles(folder.FolderId); @@ -78,7 +77,7 @@ namespace Oqtane.Modules.Admin.Files.Manager Permissions = $"{EntityNames.Folder}:{folder.FolderId}", ContentModifiedBy = file.ModifiedBy, ContentModifiedOn = file.ModifiedOn, - IsDeleted = (removed || file.IsDeleted.Value) + IsDeleted = (removed) }; searchContents.Add(searchContent); } diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs index def51519..3cd3e2bf 100644 --- a/Oqtane.Server/Repository/FileRepository.cs +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -72,7 +72,6 @@ namespace Oqtane.Repository public File AddFile(File file) { using var db = _dbContextFactory.CreateDbContext(); - file.IsDeleted = false; db.File.Add(file); db.SaveChanges(); file.Folder = _folderRepository.GetFolder(file.FolderId); diff --git a/Oqtane.Server/Repository/FolderRepository.cs b/Oqtane.Server/Repository/FolderRepository.cs index d00a6a7c..fb68033f 100644 --- a/Oqtane.Server/Repository/FolderRepository.cs +++ b/Oqtane.Server/Repository/FolderRepository.cs @@ -51,7 +51,6 @@ namespace Oqtane.Repository public Folder AddFolder(Folder folder) { using var db = _dbContextFactory.CreateDbContext(); - folder.IsDeleted = false; db.Folder.Add(folder); db.SaveChanges(); _permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.PermissionList); diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs index e395bdb5..18c320a7 100644 --- a/Oqtane.Shared/Models/File.cs +++ b/Oqtane.Shared/Models/File.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; using Oqtane.Shared; namespace Oqtane.Models @@ -55,13 +56,6 @@ namespace Oqtane.Models /// public string Description { get; set; } - /// - /// Deprecated - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework no longer uses it - /// - public bool? IsDeleted { get; set; } - /// /// Object reference to the object. /// Use this if you need to determine what the file belongs to. @@ -74,5 +68,16 @@ namespace Oqtane.Models /// [NotMapped] public string Url { get; set; } + + #region Deprecated Properties + + [Obsolete("The IsDeleted property is deprecated. Soft delete of files is not supported.", false)] + [NotMapped] + [JsonIgnore] // exclude from API payload + public bool? IsDeleted { get; set; } + + #endregion + + } } diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index 47177cdb..d8e3e1ce 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -67,13 +67,6 @@ namespace Oqtane.Models /// public string CacheControl { get; set; } - /// - /// Deprecated - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework no longer uses it - /// - public bool? IsDeleted { get; set; } - /// /// TODO: todoc what would this contain? /// @@ -110,6 +103,11 @@ namespace Oqtane.Models } } + [Obsolete("The IsDeleted property is deprecated. Soft delete of folders is not supported.", false)] + [NotMapped] + [JsonIgnore] // exclude from API payload + public bool? IsDeleted { get; set; } + #endregion } } diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs index 7f226168..750d8583 100644 --- a/Oqtane.Shared/Models/Language.cs +++ b/Oqtane.Shared/Models/Language.cs @@ -31,9 +31,8 @@ namespace Oqtane.Models /// /// Language Name - corresponds to , _not_ - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework populates it from the Culture API /// + [NotMapped] public string Name { get; set; } [NotMapped]