diff --git a/Makefile b/Makefile index 42cd34dd..6b0d5365 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,10 @@ PACKAGES_OUT=$(abspath PackagesOut) all: nuget -nuget: pclnuget basenuget sqlciphernuget staticnuget +nuget: sourcegenerator pclnuget basenuget sqlciphernuget staticnuget + +sourcegenerator: nuget\Sqlite_net.SourceGenerator\Sqlite_net.SourceGenerator.csproj $(SRC) + dotnet build -c Release $< pclnuget: nuget/SQLite-net-std/SQLite-net-std.csproj $(SRC) dotnet pack -c Release -o $(PACKAGES_OUT) $< diff --git a/SQLite.sln b/SQLite.sln index a6329bc1..8faf72b9 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -1,8 +1,11 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11012.119 d18.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A0E59A10-7BD0-4554-B133-66FA850159BE}" ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props Makefile = Makefile README.md = README.md EndProjectSection @@ -10,104 +13,136 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-std", "nuget\SQLite-net-std\SQLite-net-std.csproj", "{081D08D6-10F1-431B-88FE-469FD9FE898C}" + ProjectSection(ProjectDependencies) = postProject + {8AFC8450-42E6-41AF-8E39-17EC7C13D695} = {8AFC8450-42E6-41AF-8E39-17EC7C13D695} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDiff", "tests\ApiDiff\ApiDiff.csproj", "{1DEF735C-B973-4ED9-8446-7FFA6D0B410B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-base", "nuget\SQLite-net-base\SQLite-net-base.csproj", "{53D1953C-3641-47D0-BE08-14DB853CC576}" + ProjectSection(ProjectDependencies) = postProject + {8AFC8450-42E6-41AF-8E39-17EC7C13D695} = {8AFC8450-42E6-41AF-8E39-17EC7C13D695} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-sqlcipher", "nuget\SQLite-net-sqlcipher\SQLite-net-sqlcipher.csproj", "{59DB03EF-E28D-431E-9058-74AF316800EE}" + ProjectSection(ProjectDependencies) = postProject + {8AFC8450-42E6-41AF-8E39-17EC7C13D695} = {8AFC8450-42E6-41AF-8E39-17EC7C13D695} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.Tests", "tests\SQLite.Tests\SQLite.Tests.csproj", "{80B66A43-B358-4438-BF06-6351B86B121A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-static", "nuget\SQLite-net-static\SQLite-net-static.csproj", "{7CD60DAE-D505-4C2E-80B3-296556CE711E}" + ProjectSection(ProjectDependencies) = postProject + {8AFC8450-42E6-41AF-8E39-17EC7C13D695} = {8AFC8450-42E6-41AF-8E39-17EC7C13D695} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sqlite_net.SourceGenerator", "nuget\Sqlite_net.SourceGenerator\Sqlite_net.SourceGenerator.csproj", "{8AFC8450-42E6-41AF-8E39-17EC7C13D695}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|iPhone = Debug|iPhone Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|Any CPU = Release|Any CPU Release|iPhone = Release|iPhone Release|iPhoneSimulator = Release|iPhoneSimulator - Debug|iPhone = Debug|iPhone EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.Build.0 = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.Build.0 = Debug|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.Build.0 = Release|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhone.ActiveCfg = Release|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhone.Build.0 = Release|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.Build.0 = Debug|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.Build.0 = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.Build.0 = Debug|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.Build.0 = Release|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhone.ActiveCfg = Release|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhone.Build.0 = Release|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.Build.0 = Debug|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.Build.0 = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.Build.0 = Debug|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.Build.0 = Release|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhone.ActiveCfg = Release|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhone.Build.0 = Release|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.Build.0 = Debug|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.Build.0 = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.Build.0 = Debug|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.Build.0 = Release|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.ActiveCfg = Release|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.Build.0 = Release|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.Build.0 = Debug|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80B66A43-B358-4438-BF06-6351B86B121A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80B66A43-B358-4438-BF06-6351B86B121A}.Release|Any CPU.Build.0 = Release|Any CPU + {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhone.Build.0 = Debug|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {80B66A43-B358-4438-BF06-6351B86B121A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80B66A43-B358-4438-BF06-6351B86B121A}.Release|Any CPU.Build.0 = Release|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Release|iPhone.ActiveCfg = Release|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Release|iPhone.Build.0 = Release|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {80B66A43-B358-4438-BF06-6351B86B121A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {80B66A43-B358-4438-BF06-6351B86B121A}.Debug|iPhone.Build.0 = Debug|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|Any CPU.Build.0 = Release|Any CPU + {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhone.Build.0 = Debug|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|Any CPU.Build.0 = Release|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|iPhone.ActiveCfg = Release|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|iPhone.Build.0 = Release|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7CD60DAE-D505-4C2E-80B3-296556CE711E}.Debug|iPhone.Build.0 = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|iPhone.Build.0 = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|Any CPU.Build.0 = Release|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|iPhone.ActiveCfg = Release|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|iPhone.Build.0 = Release|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {8AFC8450-42E6-41AF-8E39-17EC7C13D695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {1DEF735C-B973-4ED9-8446-7FFA6D0B410B} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} {80B66A43-B358-4438-BF06-6351B86B121A} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E4388666-648A-41A5-B10B-2598523000A1} + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = tests\SQLite.Tests.csproj Policies = $0 diff --git a/global.json b/global.json index b3fcac52..d5bf446d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "sdk": { - "version": "9.0.100", - "rollForward": "latestFeature" - } +{ + "sdk": { + "version": "9.0.100", + "rollForward": "latestFeature" + } } \ No newline at end of file diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj index 8fd9ae2e..d9a694ee 100644 --- a/nuget/SQLite-net-base/SQLite-net-base.csproj +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -34,4 +34,10 @@ + + + + + + diff --git a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj index 714af08a..5354fbc6 100644 --- a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj +++ b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj @@ -35,4 +35,10 @@ + + + + + + diff --git a/nuget/SQLite-net-static/SQLite-net-static.csproj b/nuget/SQLite-net-static/SQLite-net-static.csproj index f8c4d857..08a691d2 100644 --- a/nuget/SQLite-net-static/SQLite-net-static.csproj +++ b/nuget/SQLite-net-static/SQLite-net-static.csproj @@ -31,4 +31,10 @@ + + + + + + diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 4a1e6d1b..23d2f776 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -33,4 +33,10 @@ + + + + + + diff --git a/nuget/Sqlite_net.SourceGenerator/Pollyfill.cs b/nuget/Sqlite_net.SourceGenerator/Pollyfill.cs new file mode 100644 index 00000000..dfa45ce1 --- /dev/null +++ b/nuget/Sqlite_net.SourceGenerator/Pollyfill.cs @@ -0,0 +1,5 @@ +// Polyfill for IsExternalInit to support record types in .NET Standard 2.0 +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} diff --git a/nuget/Sqlite_net.SourceGenerator/SQLiteFastColumnSetterGenerator.cs b/nuget/Sqlite_net.SourceGenerator/SQLiteFastColumnSetterGenerator.cs new file mode 100644 index 00000000..4985a350 --- /dev/null +++ b/nuget/Sqlite_net.SourceGenerator/SQLiteFastColumnSetterGenerator.cs @@ -0,0 +1,570 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.Diagnostics; + +[Generator] +public class SQLiteFastColumnSetterGenerator : IIncrementalGenerator +{ + private static ConcurrentDictionary, bool> cachedHasSqliteAttribute = new (); + private static ConcurrentDictionary, bool> cachedHasTableAttribute = new (); + private static List SQLitePropertyAttributes = default!; + private static ImmutableHashSet SQLitePropertyFullAttributes = default!; + + static SQLiteFastColumnSetterGenerator () + { + SQLitePropertyAttributes = new() { + "Column", + "Indexed", + "Ignore", + "Unique", + "MaxLength", + "Collation", + "NotNull", + "StoreAsText", + "AutoIncrement", + "PrimaryKey", + "NotNull" + }; + + SQLitePropertyFullAttributes = SQLitePropertyAttributes.Select (f => f + "Attribute").ToImmutableHashSet(); + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Launch Debugger for Debugging the Analyzer + // System.Diagnostics.Debugger.Launch(); + + // Find all classes with TableAttribute or properties with ColumnAttribute + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsCandidateClass(s), + transform: static (ctx, _) => GetClassInfo(ctx)) + .Where(static m => m is not null); + + // Get analyzer config options for accessing MSBuild properties + var configOptions = context.AnalyzerConfigOptionsProvider; + + // Combine with compilation + var compilationAndClasses = context.CompilationProvider + .Combine(classDeclarations.Collect()) + .Combine(configOptions); + + context.RegisterSourceOutput(compilationAndClasses, + static (spc, source) => Execute(source.Left.Left, source.Left.Right!, source.Right, spc)); + } + + static bool IsCandidateClass(SyntaxNode node) + { + if (node is not ClassDeclarationSyntax classDecl) + return false; + + // Check if class has TableAttribute + if (classDecl.AttributeLists.Any(attrList => + attrList.Attributes.Any(attr => attr.Name.ToString ().Contains("Table")))) + { + return true; + } + + // I need to analyse the base class in the semantic model + if (HasBaseClass (classDecl)) { + return true; + } + + // Check if any property has SQLite Property Attribute + return classDecl.Members + .OfType () + .Any (prop => prop.AttributeLists.Any (attrList => + attrList.Attributes.Any (attr => { + var attributeName = attr.Name.ToString (); + return SQLitePropertyAttributes.Any (f => attributeName.Contains (f)); + }))); + } + + static bool HasBaseClass (ClassDeclarationSyntax classDecl) + { + var baseList = classDecl.BaseList; + if (baseList == null) + return false; + + return baseList.Types.Count > 0; + } + + static ClassInfo? GetClassInfo (INamedTypeSymbol? classSymbol) + { + if (classSymbol is null) + return null; + + // Return null if the class is private + if (classSymbol.DeclaredAccessibility == Accessibility.Private) + return null; + + if (classSymbol.IsGenericType) + return null; + + var hasSqliteAttributes = HasTableAttribute (classSymbol); + if (!hasSqliteAttributes) { + hasSqliteAttributes = HasSQLiteAttribute (classSymbol); + } + + if (!hasSqliteAttributes) { + return null; + } + + var properties = new List (); + + // Iterate through the class hierarchy to get all properties + var currentType = classSymbol; + while (currentType != null) { + foreach (var member in currentType.GetMembers ().OfType ()) { + if (!member.IsReadOnly && !member.IsStatic && !member.IsIndexer && + (member.DeclaredAccessibility != Accessibility.Private && member.DeclaredAccessibility != Accessibility.Protected) && + (member.SetMethod?.DeclaredAccessibility != Accessibility.Private && member.SetMethod?.DeclaredAccessibility != Accessibility.Protected && member.SetMethod?.IsInitOnly != true)) { + var ignore = member.GetAttributes () + .Any (attr => IsIgnoreAttribute (attr.AttributeClass)); + + // Include property if not ignored + if (!ignore) { + var columnName = GetColumnName (member); + properties.Add (new PropertyInfo (member.Name, member.Type.ToDisplayString (), columnName, GetEnumInfo(member))); + } + } + } + + // Move to base type + currentType = currentType.BaseType; + + // Stop at System.Object or if we hit a null base type + if (currentType?.SpecialType == SpecialType.System_Object) + break; + } + + if (properties.Count == 0) + return null; + + // Handle nested classes by building the full containing type path + var containingTypes = new List (); + var currentContaining = classSymbol.ContainingType; + while (currentContaining != null) { + containingTypes.Insert (0, currentContaining.Name); + currentContaining = currentContaining.ContainingType; + } + + var fullClassName = containingTypes.Count > 0 + ? $"{string.Join (".", containingTypes)}.{classSymbol.Name}" + : classSymbol.Name; + + return new ClassInfo ( + fullClassName, + classSymbol.ContainingNamespace?.ToDisplayString () ?? string.Empty, + properties); + } + + private static EnumInfo? GetEnumInfo (IPropertySymbol member) + { + var type = member.Type; + if (type is INamedTypeSymbol named && + named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { + type = named.TypeArguments[0]; + } + + if (type.TypeKind == TypeKind.Enum) { + var storeAsText = type.GetAttributes () + .Any (attr => IsStoreAsTextAttribute(attr.AttributeClass)); + return new EnumInfo (storeAsText); + } + + return null; + } + + private static bool HasSQLiteAttribute (INamedTypeSymbol? classSymbol) + { + if (classSymbol != null && cachedHasSqliteAttribute.TryGetValue(classSymbol, out var result)) { + return result; + } + + while (true) { + if (classSymbol == null || classSymbol.SpecialType == SpecialType.System_Object) { + if (classSymbol != null) { + cachedHasSqliteAttribute[classSymbol] = false; + } + return false; + } + + var members = classSymbol.GetMembers(); + foreach (var member in members) { + if (member.GetAttributes().Any(attr => IsSQLiteAttribute (attr.AttributeClass))) + { + cachedHasSqliteAttribute[classSymbol] = true; + return true; + } + } + + classSymbol = classSymbol.BaseType; + } + } + + private static bool HasTableAttribute (INamedTypeSymbol? classSymbol) + { + if (classSymbol != null && cachedHasTableAttribute.TryGetValue (classSymbol, out var result)) { + return result; + } + + while (true) { + if (classSymbol == null || classSymbol.SpecialType == SpecialType.System_Object) { + if (classSymbol != null) { + cachedHasTableAttribute[classSymbol] = false; + } + return false; + } + + var hasTableAttribute = classSymbol.GetAttributes () + .Any (attr => IsTableAttribute (attr.AttributeClass)); + if (hasTableAttribute) { + cachedHasTableAttribute[classSymbol] = true; + return true; + } + + classSymbol = classSymbol.BaseType; + } + } + + static ClassInfo? GetClassInfo(GeneratorSyntaxContext context) + { + var classDecl = (ClassDeclarationSyntax)context.Node; + var semanticModel = context.SemanticModel; + + var classSymbol = semanticModel.GetDeclaredSymbol(classDecl); + return GetClassInfo (classSymbol); + } + + private static bool IsTableAttribute (INamedTypeSymbol? attributeClass) + { + while (true) { + if (attributeClass == null) { + return false; + } + + if (IsSQLiteNamespace (attributeClass) && attributeClass.Name == "TableAttribute") { + return true; + } + + attributeClass = attributeClass.BaseType; + } + } + + private static bool IsStoreAsTextAttribute (INamedTypeSymbol? attributeClass) + { + while (true) { + if (attributeClass == null) { + return false; + } + + if (IsSQLiteNamespace (attributeClass) && attributeClass.Name == "StoreAsTextAttribute") { + return true; + } + + attributeClass = attributeClass.BaseType; + } + } + + private static bool IsIgnoreAttribute (INamedTypeSymbol? attributeClass) + { + while (true) { + if (attributeClass == null) { + return false; + } + + if (IsSQLiteNamespace (attributeClass) && attributeClass.Name == "IgnoreAttribute") { + return true; + } + + attributeClass = attributeClass.BaseType; + } + } + + private static bool IsSQLiteAttribute (INamedTypeSymbol? attributeClass) + { + while (true) { + if (attributeClass == null) { + return false; + } + + if (IsSQLiteNamespace (attributeClass) && SQLitePropertyFullAttributes.Contains (attributeClass.Name)) { + return true; + } + + attributeClass = attributeClass.BaseType; + } + } + + private static bool IsSQLiteNamespace (INamedTypeSymbol attributeClass) + { + return attributeClass.ContainingNamespace.Name == "SQLite"; + } + + static string GetColumnName(IPropertySymbol property) + { + // Check for ColumnAttribute with name parameter + var columnAttr = property.GetAttributes() + .FirstOrDefault(attr => attr.AttributeClass?.ContainingNamespace.Name == "SQLite" && attr.AttributeClass?.Name == "ColumnAttribute"); + + if (columnAttr?.ConstructorArguments.Length > 0) + { + var nameArg = columnAttr.ConstructorArguments[0]; + if (nameArg.Value is string columnName) + return columnName; + } + + // Default to property name + return property.Name; + } + + static void Execute( + Compilation compilation, + ImmutableArray classesDuplicates, + AnalyzerConfigOptionsProvider configOptionsProvider, + SourceProductionContext context) + { + if (classesDuplicates.IsDefaultOrEmpty) + return; + + var classes = RemoveDuplicates(classesDuplicates); + + // Get the assembly name/namespace from the compilation + var assemblyName = compilation.AssemblyName ?? "Generated"; + var rootNamespace = GetRootNamespace(configOptionsProvider, compilation) ?? assemblyName; + + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("using System;"); + sb.AppendLine("using SQLite;"); + sb.AppendLine("#pragma warning disable CS0618 // Disable obsolete Warnings"); + sb.AppendLine("#pragma warning disable CS0612 // Disable obsolete Warnings"); + sb.AppendLine(); + sb.AppendLine($"namespace {rootNamespace}"); + sb.AppendLine("{"); + sb.AppendLine(" /// SQLite Initializer Class "); + sb.AppendLine(" [SQLite.Preserve(AllMembers = true)]"); + sb.AppendLine(" public static class SQLiteInitializer"); + sb.AppendLine(" {"); + sb.AppendLine(" private static bool initialized;"); + sb.AppendLine(" /// Init SQLite Fast Column Setters "); + sb.AppendLine("#if NET5_0_OR_GREATER"); + sb.AppendLine(" [System.Runtime.CompilerServices.ModuleInitializer]"); + sb.AppendLine("#endif"); + sb.AppendLine(" public static void Init()"); + sb.AppendLine(" {"); + sb.AppendLine(" if (initialized)"); + sb.AppendLine(" return;"); + sb.AppendLine(" initialized = true;"); + + foreach (var classInfo in classes) { + var fullTypeName = FullTypeName (classInfo); + var initName = "Init" + fullTypeName.Replace (".", "_"); + sb.AppendLine($" {initName}();"); + } + + sb.AppendLine (" }"); + + foreach (var classInfo in classes) { + var fullTypeName = FullTypeName (classInfo); + var initName = "Init" + fullTypeName.Replace (".", "_"); + sb.AppendLine($" /// Init SQLite Fast Column Setters for {fullTypeName}"); + sb.AppendLine($" private static void {initName}()"); + sb.AppendLine(" {"); + foreach (var property in classInfo.Properties) { + sb.AppendLine($" SQLiteConnection.RegisterFastColumnSetter("); + sb.AppendLine($" typeof({fullTypeName}),"); + sb.AppendLine($" \"{property.ColumnName}\","); + sb.Append($" (obj, stmt, index) => "); + GeneratePropertySetter ($"(({fullTypeName})obj)", sb, property); + } + sb.AppendLine (" }"); + } + + sb.AppendLine(" }"); + sb.AppendLine("}"); + + context.AddSource("SQLiteInitializer.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + private static ImmutableArray RemoveDuplicates (ImmutableArray classes) + { + Dictionary existing = new(); + + foreach (var it in classes) { + var fullTypeName = FullTypeName (it); + if (existing.TryGetValue (fullTypeName, out var current)) { + if (it.Properties.Count > current.Properties.Count) { + existing[fullTypeName] = it; + } + } + else { + existing[fullTypeName] = it; + } + } + + return existing.Values.ToImmutableArray(); + } + + static string FullTypeName (ClassInfo classInfo) + { + return string.IsNullOrEmpty (classInfo.Namespace) + ? classInfo.ClassName + : $"{classInfo.Namespace}.{classInfo.ClassName}"; +} + + static string? GetRootNamespace(AnalyzerConfigOptionsProvider configOptionsProvider, Compilation compilation) + { + if (configOptionsProvider.GlobalOptions.TryGetValue ("build_property.RootNamespace", out var rootNs)) { + return rootNs; + } + + // Fallback to assembly name + return compilation.AssemblyName; + } + + + static void GeneratePropertySetter(string typedObject, StringBuilder sb, PropertyInfo property) + { + var propertyType = property.TypeName; + + // Handle nullable types + var isNullable = propertyType.Contains("?"); + if (isNullable) + propertyType = propertyType.Replace("?", ""); + + switch (propertyType) { + case "byte[]": + case "Byte[]": + case "System.Byte[]": + isNullable = true; // need to handle nullable for byte arrays + break; + } + + if (isNullable) { + sb.AppendLine(""); + sb.AppendLine($" {{"); + sb.AppendLine($" if (SQLite3.ColumnType(stmt, index) != SQLite3.ColType.Null)"); + sb.AppendLine($" {{"); + sb.Append($" "); +} + + switch (propertyType) + { + case "string": + case "String": + case "System.String": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnString(stmt, index)"); + break; + + case "byte": + case "Byte": + case "System.Byte": + sb.Append($"{typedObject}.{property.PropertyName} = (byte)SQLite3.ColumnInt(stmt, index)"); + break; + + case "short": + case "Int16": + case "System.Int16": + sb.Append($"{typedObject}.{property.PropertyName} = (short)SQLite3.ColumnInt(stmt, index)"); + break; + + + case "int": + case "Int32": + case "System.Int32": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnInt(stmt, index)"); + break; + + case "long": + case "Int64": + case "System.Int64": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnInt64(stmt, index)"); + break; + + case "double": + case "Double": + case "System.Double": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnDouble(stmt, index)"); + break; + + case "decimal": + case "Decimal": + case "System.Decimal": + sb.Append($"{typedObject}.{property.PropertyName} = System.Convert.ToDecimal(SQLite3.ColumnDouble(stmt, index))"); + break; + + case "float": + case "Single": + case "System.Single": + sb.Append($"{typedObject}.{property.PropertyName} = (float)SQLite3.ColumnDouble(stmt, index)"); + break; + + case "bool": + case "Boolean": + case "System.Boolean": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnInt(stmt, index) == 1"); + break; + + case "DateTime": + case "System.DateTime": + sb.Append($"{typedObject}.{property.PropertyName} = new DateTime(SQLite3.ColumnInt64(stmt, index))"); + break; + + case "TimeSpan": + case "System.TimeSpan": + sb.Append($"{typedObject}.{property.PropertyName} = new TimeSpan(SQLite3.ColumnInt64(stmt, index))"); + break; + + case "Guid": + case "System.Guid": + sb.Append($"{typedObject}.{property.PropertyName} = new Guid(SQLite3.ColumnString(stmt, index))"); + break; + + case "byte[]": + case "Byte[]": + case "System.Byte[]": + sb.Append($"{typedObject}.{property.PropertyName} = SQLite3.ColumnByteArray(stmt, index)"); + break; + + default: + if (property.Enum != null) { + // For other types, try to use a generic approach + if (property.Enum.StoreAsText) { + sb.Append($"{typedObject}.{property.PropertyName} = ({propertyType})Enum.Parse(typeof({propertyType}), SQLite3.ColumnString(stmt, index), ignoreCase: true)"); + } + else { + sb.Append($"{typedObject}.{property.PropertyName} = ({propertyType})SQLite3.ColumnInt(stmt, index)"); + } + } + else { + // For other types, try to use a generic approach + sb.Append($"{typedObject}.{property.PropertyName} = ({propertyType})Convert.ChangeType(SQLite3.ColumnString(stmt, index), typeof({propertyType}))"); + } + + break; + } + + if (isNullable) { + sb.AppendLine($";"); + sb.AppendLine($" }}"); + sb.AppendLine($" }});"); + } + else { + sb.AppendLine(");"); + } + } + + record ClassInfo(string ClassName, string Namespace, List Properties); + record PropertyInfo(string PropertyName, string TypeName, string ColumnName, EnumInfo? Enum); + record EnumInfo (bool StoreAsText); +} diff --git a/nuget/Sqlite_net.SourceGenerator/Sqlite_net.SourceGenerator.csproj b/nuget/Sqlite_net.SourceGenerator/Sqlite_net.SourceGenerator.csproj new file mode 100644 index 00000000..877156d9 --- /dev/null +++ b/nuget/Sqlite_net.SourceGenerator/Sqlite_net.SourceGenerator.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + false + 9 + enable + true + true + + + + + + + + diff --git a/src/SQLite.cs b/src/SQLite.cs index 72525c56..bd95aa8a 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -25,6 +25,7 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; #if NET8_0_OR_GREATER @@ -41,6 +42,7 @@ #endif using System.Text; using System.Threading; +using ExecutionEngineException = System.ExecutionEngineException; #if USE_CSHARP_SQLITE using Sqlite3 = Community.CsharpSqlite.Sqlite3; @@ -2632,6 +2634,14 @@ void OnTableChanged (TableMapping table, NotifyTableChangedAction action) } public event EventHandler TableChanged; + + public static void RegisterFastColumnSetter ( + Type type, + string name, + Action setter) + { + FastColumnSetter.RegisterFastColumnSetter (type, name, setter); + } } public class NotifyTableChangedEventArgs : EventArgs @@ -3592,7 +3602,17 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) continue; if (fastColumnSetters[i] != null) { - fastColumnSetters[i].Invoke (obj, stmt, i); + try { + fastColumnSetters[i].Invoke (obj, stmt, i); + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException) { +#pragma warning restore CS0618 // Type or member is obsolete + // Column setter has AOT Problem so don't use it. + fastColumnSetters[i] = null; + Trace.WriteLine($"FastColumnSetter AOT Jit Exception on Type {map.MappedType.FullName} Column {cols[i].Name}"); + i--; // go one back and read it with default implementation + } } else { var colType = SQLite3.ColumnType (stmt, i); @@ -3922,6 +3942,14 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr internal class FastColumnSetter { + internal static ConcurrentDictionary<(Type, string), Action> customSetter = + new ConcurrentDictionary<(Type, string), Action>(); + + public static void RegisterFastColumnSetter(Type type, string name, Action setter) + { + customSetter[(type, name)] = setter; + } + /// /// Gets a for a generic method, suppressing AOT warnings. /// @@ -3954,7 +3982,9 @@ internal static MethodInfo GetFastSetterMethodInfoUnsafe (Type mappedType) /// internal static Action GetFastSetter (SQLiteConnection conn, TableMapping.Column column) { - Action fastSetter = null; + if (customSetter.TryGetValue ((typeof(T), column.Name), out var fastSetter)) { + return fastSetter; + } Type clrType = column.PropertyInfo.PropertyType; diff --git a/tests/SQLite.Tests/EnumCacheTest.cs b/tests/SQLite.Tests/EnumCacheTest.cs index c6163f5d..e5c1ad8b 100644 --- a/tests/SQLite.Tests/EnumCacheTest.cs +++ b/tests/SQLite.Tests/EnumCacheTest.cs @@ -1,143 +1,143 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -#if NETFX_CORE -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; -using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; -using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; -#else -using NUnit.Framework; -#endif - -namespace SQLite.Tests -{ - [TestFixture] - public class EnumCacheTests - { - [StoreAsText] - public enum TestEnumStoreAsText - { - Value1, - - Value2, - - Value3 - } - - public enum TestEnumStoreAsInt - { - Value1, - - Value2, - - Value3 +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class EnumCacheTests + { + [StoreAsText] + public enum TestEnumStoreAsText + { + Value1, + + Value2, + + Value3 + } + + public enum TestEnumStoreAsInt + { + Value1, + + Value2, + + Value3 + } + + public enum TestByteEnumStoreAsInt : byte + { + Value1, + + Value2, + + Value3 + } + + public enum TestEnumWithRepeats + { + Value1 = 1, + + Value2 = 2, + + Value2Again = 2, + + Value3 = 3, + } + + [StoreAsText] + public enum TestEnumWithRepeatsAsText + { + Value1 = 1, + + Value2 = 2, + + Value2Again = 2, + + Value3 = 3, + } + + public class TestClassThusNotEnum + { + + } + + [Test] + public void ShouldReturnTrueForEnumStoreAsText() + { + var info = EnumCache.GetInfo(); + + Assert.IsTrue(info.IsEnum); + Assert.IsTrue(info.StoreAsText); + Assert.IsNotNull(info.EnumValues); + + var values = Enum.GetValues(typeof(TestEnumStoreAsText)).Cast().ToList(); + + for (int i = 0; i < values.Count; i++) + { + Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); + } } - public enum TestByteEnumStoreAsInt : byte - { - Value1, - - Value2, - - Value3 - } - - public enum TestEnumWithRepeats - { - Value1 = 1, - - Value2 = 2, - - Value2Again = 2, - - Value3 = 3, - } - - [StoreAsText] - public enum TestEnumWithRepeatsAsText - { - Value1 = 1, - - Value2 = 2, - - Value2Again = 2, - - Value3 = 3, - } - - public class TestClassThusNotEnum - { - - } - - [Test] - public void ShouldReturnTrueForEnumStoreAsText() - { - var info = EnumCache.GetInfo(); - - Assert.IsTrue(info.IsEnum); - Assert.IsTrue(info.StoreAsText); - Assert.IsNotNull(info.EnumValues); - - var values = Enum.GetValues(typeof(TestEnumStoreAsText)).Cast().ToList(); - - for (int i = 0; i < values.Count; i++) - { - Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); - } - } - - [Test] - public void ShouldReturnTrueForEnumStoreAsInt() - { - var info = EnumCache.GetInfo(); - - Assert.IsTrue(info.IsEnum); - Assert.IsFalse(info.StoreAsText); - Assert.IsNull(info.EnumValues); + [Test] + public void ShouldReturnTrueForEnumStoreAsInt() + { + var info = EnumCache.GetInfo(); + + Assert.IsTrue(info.IsEnum); + Assert.IsFalse(info.StoreAsText); + Assert.IsNull(info.EnumValues); } - [Test] - public void ShouldReturnTrueForByteEnumStoreAsInt() - { - var info = EnumCache.GetInfo(); - - Assert.IsTrue(info.IsEnum); - Assert.IsFalse(info.StoreAsText); - } - - [Test] - public void ShouldReturnFalseForClass() - { - var info = EnumCache.GetInfo(); - - Assert.IsFalse(info.IsEnum); - Assert.IsFalse(info.StoreAsText); - Assert.IsNull(info.EnumValues); - } - - [Test] - public void Issue598_EnumsWithRepeatedValues () - { - var info = EnumCache.GetInfo (); - - Assert.IsTrue (info.IsEnum); - Assert.IsFalse (info.StoreAsText); - Assert.IsNull (info.EnumValues); - } - - [Test] - public void Issue598_EnumsWithRepeatedValuesAsText () - { - var info = EnumCache.GetInfo (); - - Assert.IsTrue (info.IsEnum); - Assert.IsTrue (info.StoreAsText); - Assert.IsNotNull (info.EnumValues); - } - } -} + [Test] + public void ShouldReturnTrueForByteEnumStoreAsInt() + { + var info = EnumCache.GetInfo(); + + Assert.IsTrue(info.IsEnum); + Assert.IsFalse(info.StoreAsText); + } + + [Test] + public void ShouldReturnFalseForClass() + { + var info = EnumCache.GetInfo(); + + Assert.IsFalse(info.IsEnum); + Assert.IsFalse(info.StoreAsText); + Assert.IsNull(info.EnumValues); + } + + [Test] + public void Issue598_EnumsWithRepeatedValues () + { + var info = EnumCache.GetInfo (); + + Assert.IsTrue (info.IsEnum); + Assert.IsFalse (info.StoreAsText); + Assert.IsNull (info.EnumValues); + } + + [Test] + public void Issue598_EnumsWithRepeatedValuesAsText () + { + var info = EnumCache.GetInfo (); + + Assert.IsTrue (info.IsEnum); + Assert.IsTrue (info.StoreAsText); + Assert.IsNotNull (info.EnumValues); + } + } +} diff --git a/tests/SQLite.Tests/FastColumnSetterTest.cs b/tests/SQLite.Tests/FastColumnSetterTest.cs new file mode 100644 index 00000000..66efa0ba --- /dev/null +++ b/tests/SQLite.Tests/FastColumnSetterTest.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class FastColumnSetterTest + { + public class TestSetter + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public string Data { get; set; } + + public DateTime Date { get; set; } + } + + public class TestDb : SQLiteConnection + { + public TestDb (String path) + : base (path) + { + CreateTable (); + } + } + + [Test] + public void SetFastColumnSetters_AndReadData() + { + FastColumnSetter.RegisterFastColumnSetter( + typeof(TestSetter), + nameof(TestSetter.Id), + (obj, stmt, index) => { ((TestSetter)obj).Id = SQLite3.ColumnInt(stmt, index); }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Data), + (obj, stmt, index) => { ((TestSetter)obj).Data = SQLite3.ColumnString (stmt, index); }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Date), + (obj, stmt, index) => { ((TestSetter)obj).Date = new DateTime (SQLite3.ColumnInt64 (stmt, index)); }); + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new TestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new TestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + } + + [Test] + public void SetFastColumnSetters_AndReadData_IsCalled() + { + int callCount = 0; + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Id), + (obj, stmt, index) => { + ((TestSetter)obj).Id = SQLite3.ColumnInt (stmt, index); + callCount++; + }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Data), + (obj, stmt, index) => { + ((TestSetter)obj).Data = SQLite3.ColumnString (stmt, index); + callCount++; + }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Date), + (obj, stmt, index) => { + ((TestSetter)obj).Date = new DateTime (SQLite3.ColumnInt64 (stmt, index)); + callCount++; + }); + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new TestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new TestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + + Assert.IsTrue(callCount > 0); + } + } +} diff --git a/tests/SQLite.Tests/SQLite.Tests.csproj b/tests/SQLite.Tests/SQLite.Tests.csproj index 5c14b15d..7404f0bf 100644 --- a/tests/SQLite.Tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests/SQLite.Tests.csproj @@ -1,19 +1,37 @@ - + net8.0 false - + 9 - - + + + + + + + + + + + + + + + SQLite.cs diff --git a/tests/SQLite.Tests/SourceGeneratorTest.cs b/tests/SQLite.Tests/SourceGeneratorTest.cs new file mode 100644 index 00000000..377942ca --- /dev/null +++ b/tests/SQLite.Tests/SourceGeneratorTest.cs @@ -0,0 +1,577 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Newtonsoft; +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +#pragma warning disable CS0618 // Disable obsolete Warnings +#pragma warning disable CS0612 // Disable obsolete Warnings + +namespace SQLite.Tests +{ + [AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + class DerivedIgnoreAttribute : IgnoreAttribute + { + } + + public class OuterTestSetter + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public string Data { get; set; } + + public DateTime Date { get; set; } + + public string NotWritable { get; } + + [Ignore] + public string Ignore { get; set; } + + [DerivedIgnore] + public string DerivedIgnore { get; set; } + + [Column("A")] + public string Z { get; set; } + + private string Private { get; set; } + public static string StaticProperty {get; set; } + + [Obsolete] + public string Obsolete { get; set; } + + public string PrivateSet { get; private set; } + + public string Init { get; init; } + } + + public class OuterTestDb : SQLiteConnection + { + public OuterTestDb (String path) + : base (path) + { + CreateTable (); + } + } + + [TestFixture] + public class SourceGeneratorTest + { + [Table("Test")] + public class StringTest : BaseTest + { + } + + public class IntTest : BaseTest + { + } + + + public partial class PartialTest + { + [PrimaryKey] + public string Id { get; set; } + } + + public partial class PartialTest + { + [Column ("Test")] + public string Test { get; set; } + } + + public class BaseTest + { + [PrimaryKey] + public T Id { get; set; } + } + + public class InnerTestSetter + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public string Data { get; set; } + + public DateTime Date { get; set; } + } + + public class AllBasicTypesSetter + { + [PrimaryKey] + public int Id { get; set; } + + public string String { get; set; } + + public byte Byte { get; set; } + + public short Short { get; set; } + + public int Int { get; set; } + + public long Long { get; set; } + + public float Float { get; set; } + + public double Double { get; set; } + + public decimal Decimal { get; set; } + + public TimeSpan TimeSpam { get; set; } + + public DateTime DateTime { get; set; } + + public Guid Guid { get; set; } + } + + + public class AllBasicTypesSetterNullable + { + [PrimaryKey] + public int Id { get; set; } + + public string String { get; set; } + + public byte? Byte { get; set; } + + public short? Short { get; set; } + + public int? Int { get; set; } + + public long? Long { get; set; } + + public float? Float { get; set; } + + public double? Double { get; set; } + + public decimal? Decimal { get; set; } + + public TimeSpan? TimeSpam { get; set; } + + public DateTime? DateTime { get; set; } + + public Guid? Guid { get; set; } + } + +// Shouldn't generate Setter because it is not accessible + private class PrivateInnerTestSetter + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public string Data { get; set; } + + public DateTime Date { get; set; } + } + + public class AllBasicTypesTestDb : SQLiteConnection + { + public AllBasicTypesTestDb (String path) + : base (path) + { + CreateTable (); + } + } + + public class AllBasicTypesNullableTestDb : SQLiteConnection + { + public AllBasicTypesNullableTestDb (String path) + : base (path) + { + CreateTable (); + } + } + + public class InnerTestDb : SQLiteConnection + { + public InnerTestDb (String path) + : base (path) + { + CreateTable (); + } + } + + [Test] + public void SqliteInitializer_PrivateInnerTestSetter () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue((typeof(PrivateInnerTestSetter), nameof(PrivateInnerTestSetter.Id)), out var setter)) + { + Assert.IsTrue(true, "Should not be registered"); + } + else + { + Assert.Fail("Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_StringTestSetter () + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (StringTest), nameof (StringTest.Id)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_PartialTestSetter () + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (PartialTest), nameof (PartialTest.Id)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_PartialTestSetter_Test() + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (PartialTest), nameof (PartialTest.Test)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_IntTestSetter () + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (IntTest), nameof (IntTest.Id)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_InnerTestSetter () + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (InnerTestSetter), nameof (InnerTestSetter.Id)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + + [Test] + public void SqliteInitializer_OuterTestSetter_ZRenamedA() + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), "A"), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_Obsolete_Property() + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof(OuterTestSetter.Obsolete)), out var setter)) { + Assert.IsTrue (true, "Should be registered"); + } + else { + Assert.Fail ("Should be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_NotWritable_NotRegistered() + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.NotWritable)), out var setter)) { + Assert.IsTrue (true, "Should not be registered (not writable)"); + } + else { + Assert.Fail ("Should not be registered (not writable)"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_Ignore_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.Ignore)), out var setter)) { + Assert.IsTrue (true, "Should not be registered (Ignore)"); + } + else { + Assert.Fail ("Should not be registered (Ignore)"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_DeriveIgnore_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.DerivedIgnore)), out var setter)) { + Assert.IsTrue (true, "Should not be registered (Ignore)"); + } + else { + Assert.Fail ("Should not be registered (Ignore)"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_Private_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), "Private"), out var setter)) { + Assert.IsTrue (true, "Private properties Should not be registered"); + } + else { + Assert.Fail ("Private properties Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_StaticProperty_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof(OuterTestSetter.StaticProperty)), out var setter)) { + Assert.IsTrue (true, "Static properties Should not be registered"); + } + else { + Assert.Fail ("Static properties Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_PrivateSet_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.PrivateSet)), out var setter)) { + Assert.IsTrue (true, "Private Set properties Should not be registered"); + } + else { + Assert.Fail ("Private Set properties Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter_Init_NotRegistered () + { + if (!SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.Init)), out var setter)) { + Assert.IsTrue (true, "Init properties Should not be registered"); + } + else { + Assert.Fail ("Init properties Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_OuterTestSetter () + { + if (SQLite.FastColumnSetter.customSetter.TryGetValue ((typeof (OuterTestSetter), nameof (OuterTestSetter.Id)), out var setter)) { + Assert.IsTrue(true, "Should not be registered"); + } + else { + Assert.Fail ("Should not be registered"); + } + } + + [Test] + public void SqliteInitializer_Inner_AndReadData() + { + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new InnerTestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new InnerTestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SetFastColumnSetters_Inner_AndReadData_IsCalled() + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new InnerTestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new InnerTestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SqliteInitializer_Outer_AndReadData () + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new OuterTestSetter() { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new OuterTestDb(TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SqliteInitializer_Outer_AndReadData_ZRenamedA() + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new OuterTestSetter () { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i), + Z = Convert.ToString(i), + }; + + var db = new OuterTestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Z.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Z, "10"); + Assert.AreEqual(mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SetFastColumnSetters_Outer_AndReadData_IsCalled () + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new OuterTestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new OuterTestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SetFastColumnSetters_AllBasicTypes_Works () + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new AllBasicTypesSetter() { + Id = i, + String = Convert.ToString(i), + Byte = (byte)i, + Short = (short)i, + Int = i, + Long = i, + Float = i, + Double = i, + Decimal = i, + DateTime = new DateTime(2000, 1, i), + TimeSpam = new TimeSpan(i, 0, 0), + Guid = new Guid (i, 0, 0, new byte[8]), + }; + + var db = new AllBasicTypesTestDb(TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Id.Equals (10)); + Assert.AreEqual (results.Count (), 1); + var data = results.FirstOrDefault (); + Assert.AreEqual (data.String, "10"); + Assert.AreEqual (data.Byte, (byte)10); + Assert.AreEqual (data.Short, (short)10); + Assert.AreEqual (data.Int, (int)10); + Assert.AreEqual (data.Long, (long)10); + Assert.AreEqual (data.Float, (float)10); + Assert.AreEqual (data.Double, (double)10); + Assert.AreEqual (data.Decimal, (decimal)10); + Assert.AreEqual (data.TimeSpam, new TimeSpan(10, 0, 0)); + Assert.AreEqual (data.DateTime, new DateTime(2000, 1, 10)); + Assert.AreEqual (data.Guid, new Guid (10, 0, 0, new byte[8])); + + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + + [Test] + public void SetFastColumnSetters_AllBasicTypesNullable_Works () + { + + var mapperCount = FastColumnSetter.customSetter.Count; + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new AllBasicTypesSetterNullable() { + Id = i, + String = null, + Byte = null, + Short = null, + Int = null, + Long = null, + Float = null, + Double = null, + Decimal = null, + DateTime = null, + TimeSpam = null, + Guid = null, + }; + + var db = new AllBasicTypesNullableTestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Id.Equals (10)); + Assert.AreEqual (results.Count (), 1); + var data = results.FirstOrDefault (); + Assert.AreEqual (data.String, null); + Assert.AreEqual (data.Byte, null); + Assert.AreEqual (data.Short, null); + Assert.AreEqual (data.Int, null); + Assert.AreEqual (data.Long, null); + Assert.AreEqual (data.Float, null); + Assert.AreEqual (data.Double, null); + Assert.AreEqual (data.Decimal, null); + Assert.AreEqual (data.TimeSpam, null); + Assert.AreEqual (data.DateTime, null); + Assert.AreEqual (data.Guid, null); + + Assert.AreEqual (mapperCount, FastColumnSetter.customSetter.Count); + } + } +}