diff --git a/.editorconfig b/.editorconfig index c0eadd3..50b1333 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,3 @@ -; What is EditorConfig? http://editorconfig.org/ - root = true ; use tabs identation for all files @@ -227,6 +225,9 @@ dotnet_diagnostic.IDE0079.severity = none # IDE0079: Remove unnecessary suppress dotnet_diagnostic.IDE0080.severity = none # IDE0080: Remove unnecessary suppression operator dotnet_diagnostic.IDE0081.severity = none # IDE0081: Remove ByVal dotnet_diagnostic.IDE0083.severity = none # IDE0083: Use pattern matching (not operator) +dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure +dotnet_diagnostic.IDE0160.severity = none # IDE0160: Use block-scoped namespace +dotnet_diagnostic.IDE0161.severity = error # IDE0161: Use file-scoped namespace dotnet_diagnostic.IDE1006.severity = none # IDE1006: Naming rule violation dotnet_diagnostic.CS1998.severity = error # CS1998: Async method lacks 'await' operators and will run synchronously diff --git a/.gitignore b/.gitignore index 5fcb5d8..eef3217 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -#Ignore files build by Visual Studio .vs/ bin/ obj/ +*.zip *.lpx *.lpx6 +*.csproj.user diff --git a/Build/BuildNuspecs.ps1 b/Build/BuildNuspecs.ps1 index 9c1e861..0d2c6f7 100644 --- a/Build/BuildNuspecs.ps1 +++ b/Build/BuildNuspecs.ps1 @@ -37,7 +37,7 @@ if ($version) { $xml.package.metadata.AppendChild($child) $child = $xml.CreateElement('copyright', $nsUri) - $child.InnerText = 'Copyright © 2016-2020 ' + $authors + $child.InnerText = 'Copyright © 2016-2023 ' + $authors $xml.package.metadata.AppendChild($child) $child = $xml.CreateElement('authors', $nsUri) diff --git a/Source/Connection.png b/Build/Connection.png similarity index 100% rename from Source/Connection.png rename to Build/Connection.png diff --git a/Source/FailedConnection.png b/Build/FailedConnection.png similarity index 100% rename from Source/FailedConnection.png rename to Build/FailedConnection.png diff --git a/Build/Pack.bat b/Build/Pack.bat new file mode 100644 index 0000000..820dcac --- /dev/null +++ b/Build/Pack.bat @@ -0,0 +1,36 @@ +ECHO OFF +ECHO Packing %2 + +DEL linq2db.LINQPad.%2 +DEL linq2db.LINQPad.%2.zip + +REM LINQPad 5 driver archive generation +IF %2 EQU lpx ( + REM remove resource satellite assemblies + RD /S /Q %1\cs + RD /S /Q %1\de + RD /S /Q %1\es + RD /S /Q %1\fr + RD /S /Q %1\it + RD /S /Q %1\ja + RD /S /Q %1\ko + RD /S /Q %1\pl + RD /S /Q %1\pt + RD /S /Q %1\pt-BR + RD /S /Q %1\ru + RD /S /Q %1\tr + RD /S /Q %1\zh-Hans + RD /S /Q %1\zh-Hant + + REM remove not needed files + DEL /Q %1\linq2db.*.xml + DEL /Q %1\*.pdb + + "C:\Program Files\7-Zip\7z.exe" -r a linq2db.LINQPad.%2.zip %1\*.* %1\..\..\..\..\Build\Connection.png %1\..\..\..\..\Build\FailedConnection.png %1\..\..\..\..\Build\header.xml +) + +REM LINQPad 7 driver archive generation +IF %2 EQU lpx6 ("C:\Program Files\7-Zip\7z.exe" a linq2db.LINQPad.%2.zip %1\linq2db.LINQPad.dll %1\..\..\..\..\Build\Connection.png %1\..\..\..\..\Build\FailedConnection.png %1\linq2db.LINQPad.deps.json) + +REN linq2db.LINQPad.%2.zip linq2db.LINQPad.%2 + diff --git a/Build/README.md b/Build/README.md index ecc2898..b8a756f 100644 --- a/Build/README.md +++ b/Build/README.md @@ -1,23 +1,25 @@ -## LINQ to DB LINQPad 6 and 7 Driver +# LINQ to DB LINQPad 7 Driver -This nuget package is a driver for [LINQPad 6 and 7](http://www.linqpad.net). +This nuget package is a driver for [LINQPad 7](http://www.linqpad.net). Support for LINQPad 6 is available via older 4.x drivers. Following databases supported: -- **DB2** (LUW, z/OS) (only 64-bit version) +- **ClickHouse**: using Binary, HTTP and MySQL interfaces +- **DB2** (LUW, z/OS): x64-bit version of LINQPad only - **Firebird** -- **Informix** (only 64-bit version) -- **Microsoft Access** *(supports both OleDb and ODBC)* -- **Microsoft Sql Server** 2005+ *(including **Microsoft Sql Azure**)* -- **Microsoft Sql Server Compact (SqlCe)** -- **MySql/MariaDB** +- **Informix**: x64-bit version of LINQPad only +- **Microsoft Access**: both OLE DB and ODBC drivers +- **Microsoft SQL Server** 2005+ *(including **Microsoft SQL Azure**)* +- **Microsoft SQL Server Compact (SQL CE)** +- **MariaDB** +- **MySql** - **Oracle** - **PostgreSQL** -- **SQLite** - **SAP HANA** *(client software must be installed, supports both Native and ODBC providers)* - **SAP/Sybase ASE** +- **SQLite** -### Installation +## Installation - Click "Add connection" in LINQPad. - In the "Choose Data Context" dialog, press the "View more drivers..." button. @@ -26,4 +28,3 @@ Following databases supported: - In the "Choose Data Context" dialog, select the "LINQ to DB" driver and click the "Next" button. - In the "LINQ to DB connection" dialog, supply your connection information. - You're done. - diff --git a/Build/azure-pipelines.yml b/Build/azure-pipelines.yml index 510ecb3..7f0146d 100644 --- a/Build/azure-pipelines.yml +++ b/Build/azure-pipelines.yml @@ -1,10 +1,10 @@ variables: solution: 'linq2db.LINQPad.sln' build_configuration: 'Release' - assemblyVersion: 4.2.0.0 - nugetVersion: 4.2.0 - nugetDevVersion: 4.2.1 - nugetPRVersion: 4.2.1 + assemblyVersion: 5.0.0.0 + nugetVersion: 5.0.0 + nugetDevVersion: 5.0.1 + nugetPRVersion: 5.0.1 artifact_lpx: 'lpx' artifact_lpx6: 'lpx6' artifact_nuget: 'nuget' @@ -98,7 +98,6 @@ stages: displayName: Publish to Azure Artifacts feed condition: eq(variables['Build.SourceBranchName'], 'master') -# apikey exires Oct/2020 - task: NuGetCommand@2 inputs: command: 'push' diff --git a/Source/header.xml b/Build/header.xml similarity index 97% rename from Source/header.xml rename to Build/header.xml index 8778755..1d210ca 100644 --- a/Source/header.xml +++ b/Build/header.xml @@ -1,5 +1,5 @@ - - - linq2db.LINQPad.dll - https://github.com/linq2db/linq2db.LINQPad - + + + linq2db.LINQPad.dll + https://github.com/linq2db/linq2db.LINQPad + diff --git a/Build/linq2db.LINQPad.nuspec b/Build/linq2db.LINQPad.nuspec index 1b0c4a2..9a9d1c1 100644 --- a/Build/linq2db.LINQPad.nuspec +++ b/Build/linq2db.LINQPad.nuspec @@ -2,48 +2,51 @@ linq2db.LINQPad - LINQ to DB driver for LINQPad 6+ - Supported databases: IBM DB2 LUW/zOS, Firebird, IBM Informix, Microsoft Access, Microsoft Sql Server (+Azure), Microsoft Sql Server Compact, MySql, MariaDB, Oracle, PostgreSQL, SQLite, SAP HANA, SAP/Sybase ASE. + LINQ to DB driver for LINQPad 7 + Supported databases: IBM DB2 LUW/zOS, Firebird, IBM Informix, Microsoft Access, Microsoft Sql Server (+Azure), Microsoft Sql Server Compact, MySql, MariaDB, Oracle, PostgreSQL, SQLite, SAP HANA, SAP/Sybase ASE, ClickHouse. - linqpaddriver linqpad linq2db linqtodb access msaccess db2 odbc oledb azure firebird informix mysql mariadb oracle postgres postgresql saphana sqlce sqlserverce sqlserver sybase ase sap sqlite database + linqpaddriver linqpad linq2db linqtodb access msaccess db2 odbc oledb azure firebird informix mysql mariadb oracle postgres postgresql saphana sqlce sqlserverce sqlserver sybase ase sap sqlite database clickhouse iseries README.md - - - + + + - - - - - - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 2165f19..58366b1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,42 +1,37 @@  - + + + + - - + - - + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - + diff --git a/LinqToDB.Templates/NotifyPropertyChanged.ttinclude b/LinqToDB.Templates/NotifyPropertyChanged.ttinclude deleted file mode 100644 index 32cad1c..0000000 --- a/LinqToDB.Templates/NotifyPropertyChanged.ttinclude +++ /dev/null @@ -1,344 +0,0 @@ -<# - { - var beforeGenerateModel = BeforeGenerateModel; - BeforeGenerateModel = () => - { - beforeGenerateModel(); - NotifyPropertyChangedImpl(); - }; - - SetPropertyValueAction += (obj,prop,val) => - { - if (prop == "IsNotifying") - obj.IsNotifying = (bool)val; - }; - } -#><#+ -public bool ImplementNotifyPropertyChanging; -public bool SkipNotifyPropertyChangedImplementation = false; - -void NotifyPropertyChangedImpl() -{ - foreach (Property prop in GetTreeNodes(Model).OfType().Where(p => p.IsNotifying).ToList()) - { - List parentMembers; - - MemberGroup gr = null; - - if (prop.Parent is Class) - { - var parent = (Class)prop.Parent; - parentMembers = parent.Members; - } - else - { - var parent = (MemberGroup)prop.Parent; - - parent.IsCompact = false; - - parentMembers = parent.Members; - - if (parent.IsPropertyGroup) - gr = parent; - } - - var name = prop.Name.Trim(); - var type = prop.BuildType().Trim(); - - if (gr == null) - { - gr = new MemberGroup - { - Region = name + " : " + type, - Members = { prop }, - IsPropertyGroup = true, - }; - - var index = parentMembers.IndexOf(prop); - - parentMembers.RemoveAt(index); - parentMembers.Insert (index, gr); - } - - gr.Conditional = prop.Conditional; - prop.Conditional = null; - - if (prop.IsAuto) - { - var field = new Field(() => type, "_" + ToCamelCase(name)) - { - AccessModifier = AccessModifier.Private, - InsertBlankLineAfter = false, - }; - - if (prop.InitValue != null) - field.InitValue = prop.InitValue; - - gr.Members.Insert(0, field); - - prop.Name = " " + name; - prop.TypeBuilder = () => " " + type; - prop.IsAuto = false; - - if (prop.HasGetter) prop.GetBodyBuilders.Add(() => new [] { "return " + field.Name + ";" }); - if (prop.HasSetter) prop.SetBodyBuilders.Add(() => new [] { field.Name + " = value;" }); - } - - var methods = new MemberGroup - { - Region = "INotifyPropertyChanged support", - Members = - { - new Field(() => "const string", "NameOf" + name) - { - InitValue = ToStringLiteral(name), - AccessModifier = AccessModifier.Public, - }, - new Field(() => "PropertyChangedEventArgs", "_" + ToCamelCase(name) + "ChangedEventArgs") - { - InitValue = "new PropertyChangedEventArgs(NameOf" + name + ")", - AccessModifier = AccessModifier.Private, - IsStatic = true, - IsReadonly = true, - }, - new Method(() => "void", "On" + name + "Changed", null, - () => new[] { "OnPropertyChanged(_" + ToCamelCase(name) + "ChangedEventArgs);" }) - { - AccessModifier = AccessModifier.Private - } - } - }; - - gr.Members.Add(methods); - - if (prop.Dependents.Count == 0) - prop.Dependents.Add(name); - - if (ImplementNotifyPropertyChanging) - { - gr.Members.Add(new MemberGroup - { - Region = "INotifyPropertyChanging support", - Members = - { - new Field(() => "PropertyChangingEventArgs", "_" + ToCamelCase(name) + "ChangingEventArgs") - { - InitValue = "new PropertyChangingEventArgs(NameOf" + name + ")", - AccessModifier = AccessModifier.Private, - IsStatic = true, - IsReadonly = true, - }, - new Method(() => "void", "On" + name + "Changing", null, - () => new[] { "OnPropertyChanging(_" + ToCamelCase(name) + "ChangingEventArgs);" }) - { - AccessModifier = AccessModifier.Private - } - } - }); - } - - if (prop.HasSetter) - { - var setBody = prop.BuildSetBody().Select(s => "\t" + s).ToArray(); - prop.SetBodyBuilders.Clear(); - prop.SetBodyBuilders.Add(() => setBody); - - string getValue; - - var getBody = prop.BuildGetBody().ToArray(); - if (getBody.Length == 1 && getBody[0].StartsWith("return")) - { - getValue = getBody[0].Substring("return".Length).Trim(' ', '\t', ';'); - } - else - { - getValue = name; - } - - var insSpaces = setBody.Length > 1; - var n = 0; - - prop.SetBodyBuilders.Insert(n++, () => new [] {"if (" + getValue + " != value)", "{" }); - - if (ImplementNotifyPropertyChanging) - { - foreach (var dp in prop.Dependents) - prop.SetBodyBuilders.Insert(n++, () => new [] { "\tOn" + dp + "Changing();" }); - prop.SetBodyBuilders.Insert(n++, () => new [] { "" }); - } - - prop.SetBodyBuilders.Insert(n++, () => new [] { "\tBefore" + name + "Changed(value);" }); - - if (insSpaces) - { - prop.SetBodyBuilders.Insert(3, () => new [] { "" }); - prop.SetBodyBuilders.Add(() => new [] { "" }); - } - - prop.SetBodyBuilders.Add(() => new [] { "\tAfter" + name + "Changed();" }); - prop.SetBodyBuilders.Add(() => new [] { "" }); - - foreach (var dp in prop.Dependents) - prop.SetBodyBuilders.Add(() => new [] { "\tOn" + dp + "Changed();" }); - - prop.SetBodyBuilders.Add(() => new [] { "}" }); - - methods.Members.Insert(0, new MemberGroup - { - IsCompact = true, - Members = - { - new Method(() => "void", "Before" + name + "Changed", new Func[] { () => type + " newValue" }) { AccessModifier = AccessModifier.Partial }, - new Method(() => "void", "After" + name + "Changed") { AccessModifier = AccessModifier.Partial }, - } - }); - } - - prop.Parent.SetTree(); - - ITree p = prop.Parent; - - while (!(p is Class) && p != null) - p = p.Parent; - - if (p != null) - { - var cl = (Class)p; - - if (!SkipNotifyPropertyChangedImplementation && !cl.Interfaces.Contains("INotifyPropertyChanged")) - { - if (!Model.Usings.Contains("System.ComponentModel")) - Model.Usings.Add("System.ComponentModel"); - - cl.Interfaces.Add("INotifyPropertyChanged"); - - cl.Members.Add(new MemberGroup - { - Region = "INotifyPropertyChanged support", - Members = - { - new Event("PropertyChangedEventHandler", "PropertyChanged", true) - { - IsVirtual = true, - Attributes = { new Attribute("field : NonSerialized") } - }, - new Method(() => "void", "OnPropertyChanged", new Func[] { () => "string propertyName" }, () => OnPropertyChangedBody) - { - AccessModifier = AccessModifier.Protected - }, - new Method(() => "void", "OnPropertyChanged", new Func[] { () => "PropertyChangedEventArgs arg" }, () => OnPropertyChangedArgBody) - { - AccessModifier = AccessModifier.Protected - }, - } - }); - } - - if (ImplementNotifyPropertyChanging && !cl.Interfaces.Contains("INotifyPropertyChanging")) - { - if (!Model.Usings.Contains("System.ComponentModel")) - Model.Usings.Add("System.ComponentModel"); - - cl.Interfaces.Add("INotifyPropertyChanging"); - - cl.Members.Add(new MemberGroup - { - Region = "INotifyPropertyChanging support", - Members = - { - new Event("PropertyChangingEventHandler", "PropertyChanging", true) - { - IsVirtual = true, - Attributes = { new Attribute("field : NonSerialized") } - }, - new Method(() => "void", "OnPropertyChanging", new Func[] { () => "string propertyName" }, () => OnPropertyChangingBody) - { - AccessModifier = AccessModifier.Protected - }, - new Method(() => "void", "OnPropertyChanging", new Func[] { () => "PropertyChangingEventArgs arg" }, () => OnPropertyChangingArgBody) - { - AccessModifier = AccessModifier.Protected - }, - } - }); - } - } - } -} - -public string[] OnPropertyChangedBody = new[] -{ - "var propertyChanged = PropertyChanged;", - "", - "if (propertyChanged != null)", - "{", - "\tpropertyChanged(this, new PropertyChangedEventArgs(propertyName));", - "}", -}; - -public string[] OnPropertyChangedArgBody = new[] -{ - "var propertyChanged = PropertyChanged;", - "", - "if (propertyChanged != null)", - "{", - "\tpropertyChanged(this, arg);", - "}", -}; - -public string[] OnPropertyChangingBody = new[] -{ - "var propertyChanging = PropertyChanging;", - "", - "if (propertyChanging != null)", - "{", - "\tpropertyChanging(this, new PropertyChangingEventArgs(propertyName));", - "}", -}; - -public string[] OnPropertyChangingArgBody = new[] -{ - "var propertyChanging = PropertyChanging;", - "", - "if (propertyChanging != null)", - "{", - "\tpropertyChanging(this, arg);", - "}", -}; - -partial class Property -{ - public bool IsNotifying; - public List Dependents = new List(); -} - -class NotifyingProperty : Property -{ - public NotifyingProperty() - { - IsNotifying = true; - } - - public NotifyingProperty(ModelType type, string name, params string[] dependents) - : base(() => type.ToTypeName(), name, null, null) - { - IsNotifying = true; - - if (dependents.Length == 0) - Dependents.Add(name); - else - Dependents.AddRange(dependents); - } - - public NotifyingProperty(string type, string name, params string[] dependents) - : base(() => type, name, null, null) - { - IsNotifying = true; - - if (dependents.Length == 0) - Dependents.Add(name); - else - Dependents.AddRange(dependents); - } -} -#> diff --git a/LinqToDB.Templates/T4Model.ttinclude b/LinqToDB.Templates/T4Model.ttinclude deleted file mode 100644 index 3d1a953..0000000 --- a/LinqToDB.Templates/T4Model.ttinclude +++ /dev/null @@ -1,1812 +0,0 @@ -<#@ assembly name="System.Core" #> -<#@ import namespace="System" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#+ -static Action WriteComment = (tt,s) => tt.WriteLine("//{0}", s); - -Action BeforeGenerateModel = () => {}; - -bool GenerateProcedureErrors = true; - -static bool EnableNullableReferenceTypes = false; -static Func IsValueType = IsValueTypeDefault; - -void GenerateModel() -{ - Model.SetTree(); - - if (GenerationEnvironment.Length > 0 && GenerationEnvironment.ToString().Trim().Length == 0) - GenerationEnvironment.Length = 0; - - WriteComment(this, "---------------------------------------------------------------------------------------------------"); - WriteComment(this, " "); - WriteComment(this, " This code was generated by T4Model template for T4 (https://github.com/linq2db/linq2db)."); - WriteComment(this, " Changes to this file may cause incorrect behavior and will be lost if the code is regenerated."); - WriteComment(this, " "); - WriteComment(this, "---------------------------------------------------------------------------------------------------"); - - WriteLine(""); - WriteLine("#pragma warning disable 1591"); - if (EnableNullableReferenceTypes) - WriteLine($"#nullable enable"); - WriteLine(""); - - BeforeGenerateModel(); - - Model.Render(this); - - WriteLine(""); - WriteLine("#pragma warning restore 1591"); -} - -void Trim() -{ - var arr = new[] { '\r', '\n', ' ' }; - while (GenerationEnvironment.Length > 0 && arr.Contains(GenerationEnvironment[GenerationEnvironment.Length - 1])) - GenerationEnvironment.Length--; - - WriteLine(""); -} - -static Action WriteUsing = (tt,s) => tt.WriteLine("using {0};", s); - -void RenderUsings(List usings) -{ - var q = - from ns in usings.Distinct() - group ns by ns.Split('.')[0]; - - var groups = - (from ns in q where ns.Key == "System" select ns).Concat - (from ns in q where ns.Key != "System" orderby ns.Key select ns); - - foreach (var gr in groups) - { - foreach (var ns in from s in gr orderby s select s) - WriteUsing(this, ns); - - WriteLine(""); - } - - Trim(); -} - -// Base data types. -// -public interface ITree -{ - ITree Parent { get; set; } - IEnumerable GetNodes(); - void SetTree(); -} - -ModelSource Model = new ModelSource(); - -public partial class ModelSource : ITree -{ - public int CurrentNamespace = 0; - - public List Usings = new List { "System" }; - public List Namespaces = new List { new Namespace() }; - - public Namespace Namespace { get { return Namespaces[CurrentNamespace]; } } - public List Types { get { return Namespaces[CurrentNamespace].Types; } } - - public virtual void Render(GeneratedTextTransformation tt) - { - tt.RenderUsings(Usings); - tt.WriteLine(""); - - foreach (var nm in Namespaces) - { - nm.Render(tt); - tt.WriteLine(""); - } - - tt.Trim(); - } - - public ITree Parent { get; set; } - public IEnumerable GetNodes() { return Namespaces; } - - public void SetTree() - { - foreach (var ch in GetNodes()) - { - ch.Parent = this; - ch.SetTree(); - } - } -} - -static Action WriteBeginNamespace = (tt,s) => { tt.WriteLine("namespace {0}", s); tt.WriteLine("{"); }; -static Action WriteEndNamespace = tt => tt.WriteLine("}"); - -public partial class Namespace : ITree -{ - public string Name; - public List Types = new List(); - public List Usings = new List(); - - public virtual void Render(GeneratedTextTransformation tt) - { - if (!string.IsNullOrEmpty(Name)) - { - WriteBeginNamespace(tt, Name); - tt.PushIndent("\t"); - } - - tt.RenderUsings(Usings); - - foreach (var t in Types) - { - t.Render(tt); - tt.WriteLine(""); - } - - tt.Trim(); - - if (!string.IsNullOrEmpty(Name)) - { - tt.PopIndent(); - WriteEndNamespace(tt); - } - } - - public ITree Parent { get; set; } - public IEnumerable GetNodes() { return Types; } - - public void SetTree() - { - foreach (var ch in GetNodes()) - { - ch.Parent = this; - ch.SetTree(); - } - } -} - -public interface IClassMember : ITree -{ -} - -public enum AccessModifier -{ - Public, - Protected, - Internal, - Private, - Partial, - None -} - -public abstract partial class TypeBase : IClassMember -{ - public AccessModifier AccessModifier = AccessModifier.Public; - public string Name; - public bool IsPartial = true; - public List Comment = new List(); - public List Attributes = new List(); - public string Conditional; - public string ClassKeyword = "class"; - - public abstract void Render(GeneratedTextTransformation tt); - - protected virtual void BeginConditional(GeneratedTextTransformation tt) - { - if (Conditional != null) - { - tt.RemoveSpace(); - tt.WriteLine("#if " + Conditional); - tt.WriteLine(""); - } - } - - protected virtual void EndConditional(GeneratedTextTransformation tt) - { - if (Conditional != null) - { - tt.RemoveSpace(); - tt.WriteLine(""); - tt.RemoveSpace(); - tt.WriteLine("#endif"); - } - } - - public ITree Parent { get; set; } - public abstract IEnumerable GetNodes(); - public abstract void SetTree (); -} - -static Action WriteBeginClass = (tt,cl) => -{ - tt.Write(cl.AccessModifier.ToString().ToLower() + " "); - if (cl.IsStatic) tt.Write("static "); - if (cl.IsPartial) tt.Write("partial ", cl.Name); - tt.Write("{0} {1}{2}", cl.ClassKeyword, cl.Name, cl.GenericArguments.Count > 0 ? $"<{string.Join(", ", cl.GenericArguments)}>" : string.Empty); - - if (!string.IsNullOrEmpty(cl.BaseClass) || cl.Interfaces.Count > 0) - { - var arr = new[] { cl.BaseClass }.Concat(cl.Interfaces) - .Where(n => n != null) - .ToArray(); - - tt.Write(" : "); - tt.Write(string.Join(", ", arr)); - } - - tt.WriteLine(""); - tt.WriteLine("{"); -}; - -static Action WriteEndClass = tt => tt.WriteLine("}"); - -public partial class Class : TypeBase -{ - public string BaseClass; - public List GenericArguments = new List(); - public bool IsStatic = false; - public List Interfaces = new List(); - public List Members = new List(); - - public Class() - { - } - - public Class(string name, params IClassMember[] members) - { - Name = name; - Members.AddRange(members); - } - - public override void Render(GeneratedTextTransformation tt) - { - BeginConditional(tt); - - foreach (var c in Comment) - tt.WriteLine("//" + c); - - if (Attributes.Count > 0) - { - var aa = Attributes.Where(a => !a.IsSeparated).ToList(); - - if (aa.Count > 0) - { - tt.Write("["); - - for (var i = 0; i < aa.Count; i++) - { - if (i > 0) SkipSpacesAndInsert(tt, ", "); - aa[i].Render(tt); - } - - tt.WriteLine("]"); - } - - aa = Attributes.Where(a => a.IsSeparated).ToList(); - - foreach (var a in aa) - { - tt.Write("["); - a.Render(tt); - tt.WriteLine("]"); - } - } - - WriteBeginClass(tt, this); - tt.PushIndent("\t"); - - foreach (var cm in Members) - { - if (cm is MemberBase) - { - var m = (MemberBase)cm; - - if (!(m is MemberGroup)) - m.BeginConditional(tt, false); - - foreach (var c in m.Comment) - WriteComment(tt, c); - - if (m.Attributes.Count > 0) - { - var q = - from a in m.Attributes - group a by a.Conditional ?? ""; - - foreach (var g in q) - { - if (g.Key.Length > 0) - { - tt.RemoveSpace(); - tt.WriteLine("#if " + g.Key); - } - - var attrs = g.ToList(); - - tt.Write("["); - - for (var i = 0; i < attrs.Count; i++) - { - if (i > 0) SkipSpacesAndInsert(tt, ", "); - attrs[i].Render(tt); - } - - tt.WriteLine("]"); - - if (g.Key.Length > 0) - { - tt.RemoveSpace(); - tt.WriteLine("#endif"); - } - } - } - - m.Render(tt, false); - if (m.InsertBlankLineAfter) - tt.WriteLine(""); - - if (!(m is MemberGroup)) - m.EndConditional(tt, false); - } - else if (cm is TypeBase) - { - var t = (TypeBase)cm; - - t.Render(tt); - tt.WriteLine(""); - } - } - - tt.Trim(); - - tt.PopIndent(); - WriteEndClass(tt); - - EndConditional(tt); - } - - public override IEnumerable GetNodes() - { - return Members; - } - - public override void SetTree() - { - foreach (var ch in GetNodes()) - { - ch.Parent = this; - ch.SetTree(); - } - } -} - -public abstract partial class MemberBase : IClassMember -{ - public string ID; - public AccessModifier AccessModifier = AccessModifier.Public; - public string Name; - public Func TypeBuilder; - public List Comment = new List(); - public string EndLineComment; - public List Attributes = new List(); - public bool InsertBlankLineAfter = true; - public string Conditional; - - public int AccessModifierLen; - public int ModifierLen; - public int TypeLen; - public int NameLen; - public int ParamLen; - public int BodyLen; - - public string Type - { - get { return TypeBuilder?.Invoke(); } - set { TypeBuilder = () => value; } - } - - public string BuildType() { return TypeBuilder?.Invoke(); } - - public virtual int CalcModifierLen() { return 0; } - public abstract int CalcBodyLen (); - public virtual int CalcParamLen () { return 0; } - public abstract void Render (GeneratedTextTransformation tt, bool isCompact); - - public virtual void BeginConditional(GeneratedTextTransformation tt, bool isCompact) - { - if (Conditional != null) - { - tt.RemoveSpace(); - tt.WriteLine("#if " + Conditional); - if (!isCompact) - tt.WriteLine(""); - } - } - - public virtual void EndConditional(GeneratedTextTransformation tt, bool isCompact) - { - if (Conditional != null) - { - tt.RemoveSpace(); - tt.WriteLine("#endif"); - if (!isCompact) - tt.WriteLine(""); - } - } - - public ITree Parent { get; set; } - public virtual IEnumerable GetNodes() { return Enumerable.Empty(); } - public virtual void SetTree () {} -} - -static Action BeginRegion = (tt,s) => { tt.WriteLine("#region {0}", s); }; -static Action EndRegion = (tt) => { tt.WriteLine("#endregion"); }; - -public partial class MemberGroup : MemberBase -{ - public string Region; - public bool IsCompact; - public bool IsPropertyGroup; - public List Members = new List(); - public List Errors = new List(); - - public override int CalcBodyLen() { return 0; } - - public override void Render(GeneratedTextTransformation tt, bool isCompact) - { - if (!string.IsNullOrEmpty(Region)) - { - BeginRegion(tt, Region); - tt.WriteLine(""); - } - - BeginConditional(tt, isCompact); - - if (Errors.Count > 0 && tt.GenerateProcedureErrors) - { - tt.RemoveSpace(); - WriteComment(tt, " Use 'GenerateProcedureErrors=false' to disable errors."); - foreach (var error in Errors) - { - tt.Error(error); - - foreach (var e in error.Split('\n')) - { - tt.RemoveSpace(); - tt.WriteLine("#error " + e.Trim('\r')); - } - } - - tt.WriteLine(""); - } - - if (IsCompact) - { - var allMembers = GetTreeNodes(this).OfType().Where(m => !(m is MemberGroup)).ToList(); - - if (allMembers.Count > 0) - { - int max = allMembers.Max(m => m.AccessModifier == AccessModifier.None ? 0 : m.AccessModifier.ToString().Length); - foreach (var m in allMembers) - m.AccessModifierLen = max; - - max = allMembers.Max(m => m.CalcModifierLen()); - foreach (var m in allMembers) - m.ModifierLen = max; - - max = allMembers.Max(m => (m.BuildType() ?? "").Length); - foreach (var m in allMembers) - m.TypeLen = max; - - var notHasGetter = allMembers.OfType().Any(m => m.IsAuto && !m.HasGetter); - var notHasSetter = allMembers.OfType().Any(m => m.IsAuto && !m.HasSetter); - - foreach (var p in allMembers.OfType()) - { - if (notHasGetter) p.GetterLen = 13; - if (notHasSetter) p.SetterLen = 13; - } - - max = allMembers.Max(m => m.Name.Length); - foreach (var m in allMembers) - m.NameLen = max; - - max = allMembers.Max(m => m.CalcParamLen()); - foreach (var m in allMembers) - m.ParamLen = max; - - max = allMembers.Max(m => m.CalcBodyLen()); - foreach (var m in allMembers) - m.BodyLen = max; - - var members = - ( - from m in allMembers - select new - { - m, - attrs = - ( - from a in m.Attributes - group a by a.Name into gr - select gr.Select((a,i) => new { a, name = a.Name + "." + i }).ToList() into s - from a in s - select a - ).ToList() - } - ).ToList(); - - var attrWeight = - ( - from m in members - from a in m.attrs - group a by a.name into gr - select new { gr.Key, Count = gr.Count() } - ).ToDictionary(a => a.Key, a => a.Count); - - var q = - from m in members - where m.attrs.Count > 0 - select new { m, w = m.attrs.Sum(aa => attrWeight[aa.name]) } into m - orderby m.w descending - select m.m; - - var attrs = new List(); - - foreach (var m in q) - { - var list = m.attrs.Select(a => a.name).ToList(); - - if (attrs.Count == 0) - attrs.AddRange(list); - else - { - for (var i = 0; i < list.Count; i++) - { - var nm = list[i]; - - if (!attrs.Contains(nm)) - { - for (var j = i + 1; j < list.Count; j++) - { - var idx = attrs.IndexOf(list[j]); - - if (idx >= 0) - { - attrs.Insert(idx, nm); - break; - } - } - } - - if (!attrs.Contains(nm)) - attrs.Add(nm); - } - } - } - - var mms = members.Select(m => - { - var arr = new Attribute[attrs.Count]; - - foreach (var a in m.attrs) - arr[attrs.IndexOf(a.name)] = a.a; - - return new { m.m, attrs = arr.ToList() }; - }).ToList(); - - var idxs = Enumerable.Range(0, attrs.Count).Select(_ => new List()).ToList(); - - for (var i = 0; i < mms.Count; i++) - for (var j = 0; j < mms[i].attrs.Count; j++) - if (mms[i].attrs[j] != null) - idxs[j].Add(i); - - var toRemove = new List(); - - for (int i = 1; i < idxs.Count; i++) - { - for (int j = 0; j < i; j++) - { - if (idxs[j] == null) - continue; - - if (idxs[i].Intersect(idxs[j]).Count() == 0) - { - foreach (var m in mms) - { - if (m.attrs[i] != null) - { - m.attrs[j] = m.attrs[i]; - m.attrs[i] = null; - } - } - - idxs[j].AddRange(idxs[i]); - idxs[i] = null; - toRemove.Add(i); - break; - } - } - - } - - foreach (var n in toRemove.OrderByDescending(i => i)) - foreach (var m in mms) - m.attrs.RemoveAt(n); - - var lens = new int[attrs.Count - toRemove.Count]; - - foreach (var m in mms) - { - for (var i = 0; i < m.attrs.Count; i++) - { - var a = m.attrs[i]; - - if (a != null) - { - var len = a.Name.Length; - - if (a.Parameters.Count >= 0) - len += a.Parameters.Sum(p => 2 + p.Length); - - lens[i] = Math.Max(lens[i], len); - } - } - } - - foreach (var m in allMembers) - { - if (!(m is MemberGroup)) - m.BeginConditional(tt, IsCompact); - - foreach (var c in m.Comment) - WriteComment(tt, c); - - if (attrs.Count > 0) - { - var ma = mms.First(mr => mr.m == m); - - if (m.Attributes.Count > 0) - { - tt.Write("["); - - for (var i = 0; i < ma.attrs.Count; i++) - { - var a = ma.attrs[i]; - - if (a == null) - { - tt.WriteSpaces(lens[i]); - if (i + 1 < ma.attrs.Count) - tt.Write(" "); - } - else - { - var len = tt.GenerationEnvironment.Length; - a.Render(tt); - len = (tt.GenerationEnvironment.Length - len); - - var commaAdded = false; - - for (var j = i + 1; j < ma.attrs.Count; j++) - { - if (ma.attrs[j] != null) - { - SkipSpacesAndInsert(tt, ", "); - commaAdded = true; - break; - } - } - - if (i + 1 < ma.attrs.Count && !commaAdded) - tt.Write(" "); - - tt.WriteSpaces(lens[i] - len); - } - } - - tt.Write("] "); - } - else - { - tt.WriteSpaces(lens.Sum() + ma.attrs.Count * 2 + 1); - } - } - - m.Render(tt, true); - - if (!IsCompact) - tt.WriteLine(""); - - if (!(m is MemberGroup)) - m.EndConditional(tt, IsCompact); - } - } - } - else - { - foreach (var cm in Members) - { - if (cm is MemberBase) - { - var m = (MemberBase)cm; - - if (!(m is MemberGroup)) - m.BeginConditional(tt, IsCompact); - - foreach (var c in m.Comment) - WriteComment(tt, c); - - if (m.Attributes.Count > 0) - { - var q = - from a in m.Attributes - group a by a.Conditional ?? ""; - - foreach (var g in q) - { - if (g.Key.Length > 0) - { - tt.RemoveSpace(); - tt.WriteLine("#if " + g.Key); - } - - var attrs = g.ToList(); - - var aa = attrs.Where(a => !a.IsSeparated).ToList(); - - if (aa.Count > 0) - { - tt.Write("["); - - for (var i = 0; i < aa.Count; i++) - { - if (i > 0) tt.Write(", "); - aa[i].Render(tt); - } - - tt.WriteLine("]"); - } - - aa = attrs.Where(a => a.IsSeparated).ToList(); - - foreach (var a in aa) - { - tt.Write("["); - a.Render(tt); - tt.WriteLine("]"); - } - - if (g.Key.Length > 0) - { - tt.RemoveSpace(); - tt.WriteLine("#endif"); - } - } - } - - m.Render(tt, false); - - if (m.InsertBlankLineAfter) - tt.WriteLine(""); - - if (!(m is MemberGroup)) - m.EndConditional(tt, IsCompact); - } - else if (cm is TypeBase) - { - var t = (TypeBase)cm; - - t.Render(tt); - tt.WriteLine(""); - } - } - } - - tt.Trim(); - - EndConditional(tt, isCompact); - - if (!string.IsNullOrEmpty(Region)) - { - tt.WriteLine(""); - EndRegion(tt); - } - } - - public override IEnumerable GetNodes() { return Members; } - - public override void SetTree() - { - foreach (var ch in GetNodes()) - { - ch.Parent = this; - ch.SetTree(); - } - } -} - -static Action WriteField = (tt,f) => -{ - var am = f.AccessModifier.ToString().ToLower(); - var mdf = - (f.IsStatic ? " static" : "") + - (f.IsReadonly ? " readonly" : "") ; - - tt.Write("{0}{1}{2}{3} {4}{5} {6}", - am, LenDiff(f.AccessModifierLen, am), - mdf, LenDiff(f.ModifierLen, mdf), - f.BuildType(), LenDiff(f.TypeLen, f.BuildType()), - f.Name); - - if (f.InitValue != null) - { - tt.Write(" = {0}", f.InitValue); - } - - tt.Write(";"); - - if (!string.IsNullOrEmpty(f.EndLineComment)) - { - tt.WriteSpaces(f.NameLen - f.Name.Length + f.BodyLen + f.ParamLen - 1); - tt.Write(" "); - WriteComment(tt, " " + f.EndLineComment); - } - else - tt.WriteLine(""); -}; - -public partial class Field : MemberBase -{ - public bool IsStatic; - public bool IsReadonly; - public string InitValue; - - public Field() - { - } - - public Field(ModelType type, string name) - { - TypeBuilder = () => type.ToTypeName(); - Name = name; - } - - public Field(Func typeBuilder, string name) - { - TypeBuilder = typeBuilder; - Name = name; - } - - public override int CalcModifierLen() - { - return - (IsStatic ? " static". Length : 0) + - (IsReadonly ? " readonly".Length : 0) ; - } - - public override int CalcBodyLen() { return InitValue == null ? 1 : 4 + InitValue.Length; } - - public override void Render(GeneratedTextTransformation tt, bool isCompact) - { - WriteField(tt, this); - } -} - -static Action WriteEvent = (tt,m) => -{ - var am = m.AccessModifier.ToString().ToLower(); - var mdf = - (m.IsStatic ? " static" : "") + - (m.IsVirtual ? " virtual" : "") + - " event"; - - tt.Write("{0}{1}{2}{3} {4}{5} {6};", - am, LenDiff(m.AccessModifierLen, am), - mdf, LenDiff(m.ModifierLen, mdf), - m.BuildType(), LenDiff(m.TypeLen, m.BuildType()), - m.Name); - - if (!string.IsNullOrEmpty(m.EndLineComment)) - { - tt.WriteSpaces(m.NameLen - m.Name.Length + m.BodyLen + m.ParamLen - 1); - tt.Write(" "); - WriteComment(tt, " " + m.EndLineComment); - } - else - tt.WriteLine(""); -}; - -public partial class Event : MemberBase -{ - public bool IsStatic; - public bool IsVirtual; - - public Event() - { - } - - public Event(Type eventType, string name, bool nullable) - { - TypeBuilder = () => new ModelType(eventType, nullable).ToTypeName(); - Name = name; - } - - public Event(string eventType, string name, bool nullable) - { - TypeBuilder = () => new ModelType(eventType, true, nullable).ToTypeName(); - Name = name; - } - - public Event(Func typeBuilder, string name) - { - TypeBuilder = typeBuilder; - Name = name; - } - - public override int CalcModifierLen() - { - return - (IsStatic ? " static". Length : 0) + - (IsVirtual ? " virtual".Length : 0) + - " event".Length; - } - - public override int CalcBodyLen() { return 1; } - - public override void Render(GeneratedTextTransformation tt, bool isCompact) - { - WriteEvent(tt, this); - } -} - -static Action WriteProperty = (tt,p,compact) => -{ - var am = p.AccessModifier == AccessModifier.None ? "" : p.AccessModifier.ToString().ToLower() + " "; - var mdf = p.IsAbstract ? "abstract " : p.IsVirtual ? "virtual " : p.IsOverride ? "override " : p.IsStatic ? "static " : ""; - - tt.Write("{0}{1}{2}{3}{4}{5} {6}", - am, LenDiff(p.AccessModifierLen, am), - mdf, LenDiff(p.ModifierLen, mdf), - p.BuildType(), LenDiff(p.TypeLen, p.BuildType()), - p.Name); - - Action writeComment = () => - { - if (!string.IsNullOrEmpty(p.EndLineComment)) - { - tt.Write(" "); - WriteComment(tt, " " + p.EndLineComment); - } - else - tt.WriteLine(""); - }; - - if (p.IsAuto) - { - tt.Write(LenDiff(p.NameLen + p.ParamLen, p.Name)); - - var len = tt.GenerationEnvironment.Length; - - tt.Write(" { "); - - if (!p.HasGetter) - tt.Write("private "); - else if (p.GetterLen == 13) - tt.Write(" "); - tt.Write("get; "); - - if (!p.HasSetter) - tt.Write("private "); - else if (p.SetterLen == 13) - tt.Write(" "); - tt.Write("set; "); - - tt.Write("}"); - - if (p.EnforceNotNullable) - tt.Write(" = null!;"); - - if (!string.IsNullOrEmpty(p.EndLineComment)) - tt.WriteSpaces(p.BodyLen - (tt.GenerationEnvironment.Length - len)); - writeComment(); - } - else - { - if (compact) - { - tt.Write(LenDiff(p.NameLen + p.ParamLen, p.Name)); - - var len = tt.GenerationEnvironment.Length; - - tt.Write(" { "); - - if (p.HasGetter) - { - tt.Write("get { "); - foreach (var t in p.BuildGetBody()) - tt.Write("{0} ", t); - tt.Write("} "); - } - - if (p.HasSetter) - { - tt.Write("set { "); - foreach (var t in p.BuildSetBody()) - tt.Write("{0} ", t); - tt.Write("} "); - } - - tt.Write("}"); - - if (!string.IsNullOrEmpty(p.EndLineComment)) - tt.WriteSpaces(p.BodyLen - (tt.GenerationEnvironment.Length - len)); - writeComment(); - } - else - { - writeComment(); - - tt.WriteLine("{"); - tt.PushIndent("\t"); - - if (p.HasGetter) - { - var getBody = p.BuildGetBody().ToArray(); - if (getBody.Length == 1) - { - tt.WriteLine("get {{ {0} }}", getBody[0]); - } - else - { - tt.WriteLine("get"); - tt.WriteLine("{"); - tt.PushIndent("\t"); - - foreach (var t in getBody) - tt.WriteLine(t); - - tt.PopIndent(); - tt.WriteLine("}"); - } - } - - if (p.HasSetter) - { - var setBody = p.BuildSetBody().ToArray(); - if (setBody.Length == 1) - { - tt.WriteLine("set {{ {0} }}", setBody[0]); - } - else - { - tt.WriteLine("set"); - tt.WriteLine("{"); - tt.PushIndent("\t"); - - foreach (var t in setBody) - tt.WriteLine(t); - - tt.PopIndent(); - tt.WriteLine("}"); - } - } - - tt.PopIndent(); - tt.WriteLine("}"); - } - } -}; - -public partial class Property : MemberBase -{ - public bool IsAuto = true; - public string InitValue; - public bool IsVirtual; - public bool IsOverride; - public bool IsAbstract; - public bool IsStatic; - public bool HasGetter = true; - public bool HasSetter = true; - public List>> GetBodyBuilders = new List>>(); - public List>> SetBodyBuilders = new List>>(); - - public int GetterLen = 5; - public int SetterLen = 5; - - public Property() - { - } - - public Property(ModelType type, string name, Func> getBodyBuilder = null, Func> setBodyBuilder = null) - { - TypeBuilder = () => type.ToTypeName(); - Name = name; - - InitBody(getBodyBuilder, setBodyBuilder); - } - - public Property(bool enforceNotNullable, Func typeBuilder, string name, Func> getBodyBuilder = null, Func> setBodyBuilder = null) - : this(typeBuilder, name, getBodyBuilder, setBodyBuilder) - { - EnforceNotNullable = enforceNotNullable; - } - - public Property(Func typeBuilder, string name, Func> getBodyBuilder = null, Func> setBodyBuilder = null) - { - TypeBuilder = typeBuilder; - Name = name; - - InitBody(getBodyBuilder, setBodyBuilder); - } - - public override int CalcModifierLen() - { - return IsVirtual ? " virtual".Length : 0; - } - - public override int CalcBodyLen() - { - if (IsAuto) - return 4 + GetterLen + SetterLen; // ' { get; set; }' - - var len = " {".Length; - - if (HasGetter) - { - len += " get {".Length; - foreach (var t in BuildGetBody()) - len += 1 + t.Length; - len += " }".Length; - } - - if (HasSetter) - { - len += " set {".Length; - foreach (var t in BuildSetBody()) - len += 1 + t.Length; - len += " }".Length; - } - - len += " }".Length; - - return len; - } - - public override void Render(GeneratedTextTransformation tt, bool isCompact) - { - if (!IsAuto && HasGetter) - { - var getBody = BuildGetBody().ToArray(); - if (getBody.Length == 1) - { - var t = getBody[0]; - - if (!t.StartsWith("return")) - { - t = "return " + t; - - if (!t.EndsWith(";")) - t += ";"; - - GetBodyBuilders.Clear(); - GetBodyBuilders.Add(() => new [] { t }); - } - } - } - - WriteProperty(tt, this, isCompact); - } - - public Property InitBody(Func> getBodyBuilder = null, Func> setBodyBuilder = null) - { - IsAuto = getBodyBuilder == null && setBodyBuilder == null; - - if (getBodyBuilder != null) GetBodyBuilders.Add(getBodyBuilder); - if (setBodyBuilder != null) SetBodyBuilders.Add(setBodyBuilder); - - if (!IsAuto) - { - HasGetter = getBodyBuilder != null; - HasSetter = setBodyBuilder != null; - } - - return this; - } - - public Property InitGetter(Func> getBodyBuilder) - { - return InitBody(getBodyBuilder, null); - } - - public IEnumerable BuildGetBody() - { - return GetBodyBuilders.SelectMany(builder => builder?.Invoke() ?? Array.Empty()); - } - - public IEnumerable BuildSetBody() - { - return SetBodyBuilders.SelectMany(builder => builder?.Invoke() ?? Array.Empty()); - } - - protected internal virtual bool EnforceNotNullable { get; } -} - -static Action WriteMethod = (tt,m,compact) => -{ - var am1 = m.AccessModifier.ToString().ToLower(); - var len1 = m.AccessModifierLen; - var am2 = ""; - var len2 = 0; - var mdf = m.IsAbstract ? " abstract" : m.IsVirtual ? " virtual" : m.IsOverride ? " override" : m.IsStatic ? " static" : ""; - var mlen = m.ModifierLen; - - if (am1 == "partial" && mdf.Length > 0) - { - am2 = " " + am1; len2 = len1 + 1; - am1 = ""; len1 = 0; - mdf = mdf.Trim(); - mlen--; - } - - tt.Write("{0}{1}{2}{3}{4}{5}{6}{7}{8} {9}{10}", - am1, LenDiff(len1, am1), - mdf, LenDiff(mlen, mdf), - am2, LenDiff(len2, am2), - m.BuildType() == null ? "" : " ", - m.BuildType(), LenDiff(m.TypeLen, m.BuildType() ?? ""), - m.Name, - m.GenericArguments.Count > 0 ? $"<{string.Join(", ", m.GenericArguments)}>" : string.Empty); - - Action writeComment = () => - { - if (!string.IsNullOrEmpty(m.EndLineComment)) - { - tt.Write(" "); - WriteComment(tt, " " + m.EndLineComment); - } - else - tt.WriteLine(""); - }; - - Action writeParams = () => - { - tt.Write("("); - - for (int i = 0; i < m.ParameterBuilders.Count; i++) - { - if (i > 0) - tt.Write(", "); - tt.Write(m.ParameterBuilders[i]()); - } - - tt.Write(")"); - }; - - if (compact) - { - tt.Write(LenDiff(m.NameLen, m.Name)); - - var len = tt.GenerationEnvironment.Length; - - writeParams(); - - foreach (var s in m.AfterSignature) - { - tt.Write(" "); - tt.Write(s); - } - - len = tt.GenerationEnvironment.Length - len; - - if (m.IsAbstract || m.AccessModifier == AccessModifier.Partial) - { - tt.Write(";"); - len = 0; - } - else - { - tt.WriteSpaces(m.ParamLen - len); - - len = tt.GenerationEnvironment.Length; - - tt.Write(" {"); - - foreach (var t in m.BuildBody()) - tt.Write(" {0}", t); - - tt.Write(" }"); - } - - if (!string.IsNullOrEmpty(m.EndLineComment)) - tt.WriteSpaces(m.BodyLen - (tt.GenerationEnvironment.Length - len)); - writeComment(); - } - else - { - writeParams (); - writeComment(); - - tt.PushIndent("\t"); - foreach (var s in m.AfterSignature) - tt.WriteLine(s); - tt.PopIndent(); - - tt.WriteLine("{"); - tt.PushIndent("\t"); - - foreach (var t in m.BuildBody()) - { - if (t.Length > 1 && t[0] == '#') - { - tt.RemoveSpace(); - } - - tt.WriteLine(t); - } - - tt.PopIndent(); - tt.WriteLine("}"); - } -}; - -public partial class Method : MemberBase -{ - public bool IsAbstract; - public bool IsVirtual; - public bool IsOverride; - public bool IsStatic; - public List GenericArguments = new List(); - public List AfterSignature = new List(); - public List> ParameterBuilders = new List>(); - public List>> BodyBuilders = new List>>(); - - public Method() - { - } - - public Method(Func typeBuilder, string name, IEnumerable> parameterBuilders = null, params Func>[] bodyBuilders) - { - TypeBuilder = typeBuilder; - Name = name; - - if (parameterBuilders != null) ParameterBuilders.AddRange(parameterBuilders); - if (bodyBuilders != null) BodyBuilders.AddRange(bodyBuilders); - } - - public static Method Create(string type, string name, IEnumerable parameters = null, IEnumerable body = null) - { - return new Method( - () => type, - name, - parameters?.Select>((string p) => () => p), - body?.Select>>(p => () => new[] { p }).ToArray()); - } - - public IEnumerable BuildBody() - { - return BodyBuilders.SelectMany(builder => builder?.Invoke() ?? Array.Empty()); - } - - public override int CalcModifierLen() - { - return - IsAbstract ? " abstract".Length : - IsVirtual ? " virtual".Length : - IsStatic ? " static".Length : 0; - } - - public override int CalcBodyLen() - { - if (IsAbstract || AccessModifier == AccessModifier.Partial) - return 1; - - var len = " {".Length; - - foreach (var t in BuildBody()) - len += 1 + t.Length; - - len += " }".Length; - - return len; - } - - public override int CalcParamLen() - { - return ParameterBuilders.Sum(p => p().Length + 2); - } - - public override void Render(GeneratedTextTransformation tt, bool isCompact) - { - WriteMethod(tt, this, isCompact); - } -} - -static Action WriteAttribute = (tt,a) => -{ - tt.Write(a.Name); - - if (a.Parameters.Count > 0) - { - tt.Write("("); - - for (var i = 0; i < a.Parameters.Count; i++) - { - if (i > 0) - if (a.Parameters[i - 1].All(c => c == ' ')) - tt.Write(" "); - else - SkipSpacesAndInsert(tt, ", "); - tt.Write(a.Parameters[i]); - } - - SkipSpacesAndInsert(tt, ")"); - } -}; - -public partial class Attribute -{ - public string Name; - public List Parameters = new List(); - public string Conditional; - public bool IsSeparated; - - public Attribute() - { - } - - public Attribute(string name, params string[] ps) - { - Name = name; - Parameters.AddRange(ps); - } - - public virtual void Render(GeneratedTextTransformation tt) - { - WriteAttribute(tt, this); - } -} - -// Helpers. -// - -Func ToPlural = s => s + "s"; -Func ToSingular = s => s; - -static string LenDiff(int max, string str) -{ - var s = ""; - - while (max-- > str.Length) - s += " "; - - return s; -} - -public void WriteSpaces(int len) -{ - while (len-- > 0) - Write(" "); -} - -void RemoveSpace() -{ - Write(" "); - - while (GenerationEnvironment.Length > 0 && - (GenerationEnvironment[GenerationEnvironment.Length - 1] == ' ' || - GenerationEnvironment[GenerationEnvironment.Length - 1] == '\t')) - GenerationEnvironment.Length--; -} - -public static IEnumerable GetTreeNodes(ITree parent) -{ - foreach (var node in parent.GetNodes()) - { - yield return node; - - foreach (var grandNode in GetTreeNodes(node)) - yield return grandNode; - } -} - -public static ITree FindNode(ITree parent, Func func) -{ - foreach (var node in parent.GetNodes()) - { - if (func(node)) - return node; - - var n = FindNode(node, func); - - if (n != null) - return n; - } - - return null; -} - -static void SkipSpacesAndInsert(GeneratedTextTransformation tt, string value) -{ - var l = tt.GenerationEnvironment.Length; - - for (; l > 0 && tt.GenerationEnvironment[l - 1] == ' '; l--) - { - } - - tt.GenerationEnvironment.Insert(l, value); -} - - -string ToCamelCase(string name) -{ - var n = 0; - - foreach (var c in name) - { - if (char.IsUpper(c)) - n++; - else - break; - } - - if (n == 0) - return name; - - if (n == name.Length) - return name.ToLower(); - - n = Math.Max(1, n - 1); - - return name.Substring(0, n).ToLower() + name.Substring(n); -} - -event Action SetPropertyValueAction; - -void SetPropertyValue(Property propertyObject, string propertyName, object value) -{ - if (SetPropertyValueAction != null) - SetPropertyValueAction(propertyObject, propertyName, value); -} - -static string ToStringLiteral(string value) -{ - if (value == null) - return "null"; - - var sb = new StringBuilder("\""); - - foreach (var chr in value) - { - switch (chr) - { - case '\t': sb.Append("\\t"); break; - case '\n': sb.Append("\\n"); break; - case '\r': sb.Append("\\r"); break; - case '\\': sb.Append("\\\\"); break; - case '"' : sb.Append("\\\""); break; - case '\0': sb.Append("\\0"); break; - case '\u0085': - case '\u2028': - case '\u2029': - sb.Append($"\\u{(ushort)chr:X4}"); break; - default: sb.Append(chr); break; - } - } - - sb.Append('"'); - - return sb.ToString(); -} - -public class ModelType -{ - private readonly IList _arguments = new List(); - - public static ModelType Create(bool referenceNullable) - { - return Create(typeof(TType), referenceNullable); - } - - public static ModelType Create(Type type, bool referenceNullable) - { - if (type.IsArray) - return Array(Create(type.GetElementType(), false), referenceNullable); - - return new ModelType(type, referenceNullable); - } - - public static ModelType Array(ModelType elementType, bool referenceNullable) - { - return new ModelType(elementType, referenceNullable); - } - - public ModelType(Type type, bool nullable, params ModelType[] typeArguments) - { - if (type.IsConstructedGenericType) - { - if (typeArguments != null && typeArguments.Length > 0) - throw new ArgumentException($"{type} must be open generic type or {typeArguments} should be empty"); - - if (!_aliasedTypes.ContainsKey(type)) - _arguments = new List(type.GetGenericArguments().Select(a => new ModelType(a, /* we don't have type info here */ false))); - } - - Type = type; - IsReference = !type.IsValueType; - IsNullable = nullable || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)); - if (typeArguments != null && typeArguments.Length > 0) - _arguments = new List(typeArguments); - } - - public ModelType(string type, bool referenceType, bool isNullable, params ModelType[] typeArguments) - { - TypeName = type; - IsReference = referenceType; - IsNullable = isNullable; - - if (typeArguments != null && typeArguments.Length > 0) - _arguments = new List(typeArguments); - } - - // array constructor - public ModelType(ModelType elementType, bool isNullable) - { - ElementType = elementType; - IsReference = true; - IsNullable = isNullable; - IsArray = true; - } - - public Type Type { get; } - public string TypeName { get; } - public ModelType ElementType { get; } - public bool IsReference { get; } - public bool IsNullable { get; } - public bool IsArray { get; } - - public IEnumerable Arguments => _arguments ?? System.Array.Empty(); - - private static readonly IDictionary _aliasedTypes = new Dictionary() - { - { typeof(bool), "bool" }, - { typeof(byte), "byte" }, - { typeof(sbyte), "sbyte" }, - { typeof(char), "char" }, - { typeof(decimal), "decimal" }, - { typeof(double), "double" }, - { typeof(float), "float" }, - { typeof(int), "int" }, - { typeof(uint), "uint" }, - { typeof(long), "long" }, - { typeof(ulong), "ulong" }, - { typeof(object), "object" }, - { typeof(short), "short" }, - { typeof(ushort), "ushort" }, - { typeof(string), "string" }, - { typeof(bool?), "bool?" }, - { typeof(byte?), "byte?" }, - { typeof(sbyte?), "sbyte?" }, - { typeof(char?), "char?" }, - { typeof(decimal?), "decimal?" }, - { typeof(double?), "double?" }, - { typeof(float?), "float?" }, - { typeof(int?), "int?" }, - { typeof(uint?), "uint?" }, - { typeof(long?), "long?" }, - { typeof(ulong?), "ulong?" }, - { typeof(short?), "short?" }, - { typeof(ushort?), "ushort?" } - }; - - public string ToTypeName() - { - var sb = new StringBuilder(); - - if (TypeName != null) - sb.Append(TypeName); - else if (Type != null) - sb.Append(_aliasedTypes.ContainsKey(Type) ? _aliasedTypes[Type] : (Type.Name.Substring(0, Type.Name.IndexOf('`') < 0 ? Type.Name.Length : Type.Name.IndexOf('`')))); - else - sb.Append(ElementType.ToTypeName()); - - if (_arguments != null && _arguments.Count > 0) - { - sb.Append("<"); - sb.Append(string.Join(", ", _arguments.Select(a => a.ToTypeName()))); - sb.Append(">"); - } - - if (IsArray) - sb.Append("[]"); - - if (EnableNullableReferenceTypes && IsReference && IsNullable) - sb.Append("?"); - - return sb.ToString(); - } -} - -static bool IsValueTypeDefault(string typeName) -{ - switch (typeName) - { - case "bool": - case "bool?": - case "char": - case "char?": - case "decimal": - case "decimal?": - case "int": - case "int?": - case "uint": - case "uint?": - case "byte": - case "byte?": - case "sbyte": - case "sbyte?": - case "long": - case "long?": - case "ulong": - case "ulong?": - case "short": - case "short?": - case "ushort": - case "ushort?": - case "float": - case "float?": - case "double": - case "double?": - case "DateTime": - case "DateTime?": - case "DateTimeOffset": - case "DateTimeOffset?": - case "TimeSpan": - case "TimeSpan?": - case "Guid": - case "Guid?": - case "SqlHierarchyId": - case "SqlHierarchyId?": - case "NpgsqlDate": - case "NpgsqlDate?": - case "NpgsqlTimeSpan": - case "NpgsqlTimeSpan?": - case "NpgsqlPoint": - case "NpgsqlPoint?": - case "NpgsqlLSeg": - case "NpgsqlLSeg?": - case "NpgsqlBox": - case "NpgsqlBox?": - case "NpgsqlPath": - case "NpgsqlPath?": - case "NpgsqlPolygon": - case "NpgsqlPolygon?": - case "NpgsqlCircle": - case "NpgsqlCircle?": - case "NpgsqlLine": - case "NpgsqlLine?": - case "NpgsqlInet": - case "NpgsqlInet?": - case "NpgsqlDateTime": - case "NpgsqlDateTime?": - return true; - case "object": - case "string": - case "byte[]": - case "BitArray": - case "SqlGeography": - case "SqlGeometry": - case "PhysicalAddress": - case "Array": - case "DataTable": - return false; - } - - return typeName.EndsWith("?"); -} - -#> diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt index 6223674..120733c 100644 --- a/MIT-LICENSE.txt +++ b/MIT-LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2016-2022 Linq To DB Team +Copyright (c) 2016-2023 Linq To DB Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 860e4b4..0e59b15 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ -## LINQ to DB LINQPad Driver +# LINQ to DB LINQPad Driver [![NuGet Version and Downloads count](https://buildstats.info/nuget/linq2db.LINQPad?includePreReleases=true)](https://www.nuget.org/packages/linq2db.LINQPad) [![License](https://img.shields.io/github/license/linq2db/linq2db.LINQPad)](MIT-LICENSE.txt) [![Master branch build](https://img.shields.io/azure-devops/build/linq2db/linq2db/8/master?label=build%20(master))](https://dev.azure.com/linq2db/linq2db/_build?definitionId=8&_a=summary) [![Latest build](https://img.shields.io/azure-devops/build/linq2db/linq2db/8?label=build%20(latest))](https://dev.azure.com/linq2db/linq2db/_build?definitionId=8&_a=summary) -linq2db.LINQPad is a driver for [LINQPad 5 (.NET Framework)](http://www.linqpad.net) and [LINQPad 6-7 (.NET Core)](http://www.linqpad.net). +linq2db.LINQPad is a driver for [LINQPad 5 (.NET Framework 4.8)](http://www.linqpad.net) and [LINQPad 7 (.NET 6+)](http://www.linqpad.net). -Following databases supported (by all LINQPad versions if other not noted): +Following databases supported (by all LINQPad versions if other not specified): -- **DB2** (LUW, z/OS) (LINQPad 6+ supports only 64-bit version) -- **DB2 iSeries** (using [3rd-party provider](https://github.com/LinqToDB4iSeries/Linq2DB4iSeries)) *(iAccess 7.1+ software must be installed)*. **IMPORTANT:** currently available only for LINQPad 5 using linq2db.LINQPad version 2.9.3 or earlier +- **ClickHouse**: using Binary (LINQPad 7), HTTP and MySQL interfaces +- **DB2** (LUW, z/OS): LINQPad 7 x64 and LINQPad 5 x86 - **Firebird** -- **Informix** (LINQPad 6+ supports only 64-bit version) -- **Microsoft Access** *(supports both OleDb and ODBC)* -- **Microsoft Sql Server** 2000+ *(including **Microsoft Sql Azure**. LINQPad 6+ [doesn't support](https://stackoverflow.com/a/45418196) **Sql Server 2000**)* -- **Microsoft Sql Server Compact (SqlCe)** -- **MySql/MariaDB** +- **Informix**: LINQPad 7 x64 and LINQPad 5 x86 +- **Microsoft Access**: both OLE DB and ODBC drivers +- **Microsoft SQL Server** 2005+ *(including **Microsoft SQL Azure**)* +- **Microsoft SQL Server Compact (SQL CE)** +- **MariaDB** +- **MySql** - **Oracle** - **PostgreSQL** -- **SQLite** - **SAP HANA** *(client software must be installed, supports both Native and ODBC providers)* - **SAP/Sybase ASE** +- **SQLite** -### Download +## Download -Releases are hosted on [Github](https://github.com/linq2db/linq2db.LINQPad/releases) and on [Nuget](https://www.nuget.org/packages/linq2db.LINQPad) for LINQPad 6+ driver. +Releases are hosted on [Github](https://github.com/linq2db/linq2db.LINQPad/releases) and on [nuget.org](https://www.nuget.org/packages/linq2db.LINQPad) for LINQPad 7 driver. Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2db/_packaging?_a=package&feed=linq2db%40Local&package=linq2db.LINQPad&protocolType=NuGet). Feed [URL](https://pkgs.dev.azure.com/linq2db/linq2db/_packaging/linq2db/nuget/v3/index.json) ([how to use](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio#package-sources)). +## Installation -### Installation - -#### LINQPad 6+ (NuGet) +### LINQPad 7 (NuGet) - Click "Add connection" in LINQPad. - In the "Choose Data Context" dialog, press the "View more drivers..." button. @@ -41,7 +41,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d - In the "LINQ to DB connection" dialog, supply your connection information. - You're done. -#### LINQPad 6+ (Manual) +### LINQPad 7 (Manual) - Download latest **.lpx6** file from the link provided above. - Click "Add connection" in LINQPad. @@ -52,7 +52,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d - In the "LINQ to DB connection" dialog, supply your connection information. - You're done. -#### LINQPad 5 (Choose a driver) +### LINQPad 5 (Choose a driver) - Click "Add connection" in LINQPad. - In the "Choose Data Context" dialog, press the "View more drivers..." button. @@ -62,7 +62,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d - In the "LINQ to DB connection" dialog, supply your connection information. - You're done. -#### LINQPad 5 (Manual) +### LINQPad 5 (Manual) - Download latest **.lpx** file from the link provided above. - Click "Add connection" in LINQPad. diff --git a/Source/AppJsonConfig.cs b/Source/AppJsonConfig.cs deleted file mode 100644 index fd17baa..0000000 --- a/Source/AppJsonConfig.cs +++ /dev/null @@ -1,56 +0,0 @@ -#if NETCORE -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json; -using LinqToDB.Configuration; - -namespace LinqToDB.LINQPad -{ - internal class AppJsonConfig : ILinqToDBSettings - { - public static ILinqToDBSettings Load(string configPath) - { - var config = JsonSerializer.Deserialize(File.ReadAllText(configPath)); - - return new AppJsonConfig(config?.ConnectionStrings?.Select(entry => (IConnectionStringSettings)new ConnectionStringSettings(entry.Key, entry.Value)).ToArray() - ?? Array.Empty()); - } - - private readonly IConnectionStringSettings[] _connectionStrings; - - public AppJsonConfig(IConnectionStringSettings[] connectionStrings) - { - _connectionStrings = connectionStrings; - } - - IEnumerable ILinqToDBSettings.DataProviders => Array.Empty(); - string? ILinqToDBSettings.DefaultConfiguration => null; - string? ILinqToDBSettings.DefaultDataProvider => null; - IEnumerable ILinqToDBSettings.ConnectionStrings => _connectionStrings; - - private class JsonConfig - { - public IDictionary? ConnectionStrings { get; set; } - } - - private class ConnectionStringSettings : IConnectionStringSettings - { - private readonly string _name; - private readonly string _connectionString; - - public ConnectionStringSettings(string name, string connectionString) - { - _name = name; - _connectionString = connectionString; - } - - string IConnectionStringSettings.ConnectionString => _connectionString; - string IConnectionStringSettings.Name => _name; - string? IConnectionStringSettings.ProviderName => null; - bool IConnectionStringSettings.IsGlobal => false; - } - } -} -#endif diff --git a/Source/CSharpTools.cs b/Source/CSharpTools.cs deleted file mode 100644 index 5b36319..0000000 --- a/Source/CSharpTools.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Text; - -namespace LinqToDB.LINQPad -{ - // added temporary from https://github.com/linq2db/linq2db/pull/1393 - internal static class CSharpTools - { - /// - /// Reserved words (keywords) taken from - /// . - /// List actual for C# 8.0. - /// - private static readonly ISet _reservedWords - = new HashSet() - { - "abstract", "as", "base", "bool", "break", "byte", "case", "catch", - "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", - "do", "double", "else", "enum", "event", "explicit", "extern", "false", - "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", - "in", "int", "interface", "internal", "is", "lock", "long", "namespace", - "new", "null", "object", "operator", "out", "override", "params", "private", - "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", - "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", - "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", - "using", "virtual", "void", "volatile", "while" - }; - - /// - /// Contextual words taken from - /// . - /// List actual for C# 7.3. - /// - private static readonly ISet _contextualWords - = new HashSet() - { - "add", "alias", "ascending", "async", "await", "by", "descending", "dynamic", - "equals", "from", "get", "global", "group", "into", "join", "let", - "nameof", "on", "orderby", "partial", "remove", "select", "set", "unmanaged", - "value", "var", "when", "where", "yield" - }; - - private static readonly ISet _otherCharsCategories; - - private static readonly ISet _startCharCategories; - - static CSharpTools() - { - _startCharCategories = new HashSet() - { - // Lu letter - UnicodeCategory.UppercaseLetter, - // Ll letter - UnicodeCategory.LowercaseLetter, - // Lt letter - UnicodeCategory.TitlecaseLetter, - // Lm letter - UnicodeCategory.ModifierLetter, - // Lo letter - UnicodeCategory.OtherLetter, - // Nl letter - UnicodeCategory.LetterNumber - }; - - _otherCharsCategories = new HashSet(_startCharCategories) - { - // Mn - UnicodeCategory.NonSpacingMark, - // Mc - UnicodeCategory.SpacingCombiningMark, - // Nd - UnicodeCategory.DecimalDigitNumber, - // Pc - UnicodeCategory.ConnectorPunctuation, - // Cf - UnicodeCategory.Format - }; - } - - /// - /// Converts to valid C# identifier. - /// - public static string ToValidIdentifier(string? name) - { - if (name == null || name == string.Empty || name == "@") - { - return "_"; - } - - if (_reservedWords.Contains(name) || _contextualWords.Contains(name)) - { - return "@" + name; - } - - if (name.StartsWith("@")) - { - if (_reservedWords.Contains(name.Substring(1)) || _contextualWords.Contains(name.Substring(1))) - { - return name; - } - else - { - name = name.Substring(1); - } - } - - var sb = new StringBuilder(); - - foreach (var chr in name) - { - var cat = CharUnicodeInfo.GetUnicodeCategory(chr); - if (sb.Length == 0 && !_startCharCategories.Contains(cat) && chr != '_') - { - sb.Append('_'); - } - - if (sb.Length != 0 && !_otherCharsCategories.Contains(cat)) - { - sb.Append('_'); - } - else - { - sb.Append(chr); - } - } - - if (sb.Length >= 2 && sb[0] == '_' && sb[1] == '_' && (sb.Length == 2 || sb[2] != '_')) - { - sb.Insert(0, '_'); - } - - return sb.ToString(); - } - - public static string ToStringLiteral(string? value) - { - if (value == null) - return "null"; - - var sb = new StringBuilder("\""); - - foreach (var chr in value) - { - switch (chr) - { - case '\t': sb.Append("\\t"); break; - case '\n': sb.Append("\\n"); break; - case '\r': sb.Append("\\r"); break; - case '\\': sb.Append("\\\\"); break; - case '"': sb.Append("\\\""); break; - case '\0': sb.Append("\\0"); break; - case '\u0085': - case '\u2028': - case '\u2029': - sb.Append($"\\u{(ushort)chr:X4}"); break; - default: sb.Append(chr); break; - } - } - - sb.Append('"'); - - return sb.ToString(); - } - } -} diff --git a/Source/CX.cs b/Source/CX.cs deleted file mode 100644 index 8c09dea..0000000 --- a/Source/CX.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace LinqToDB.LINQPad -{ - // context properties names - static class CX - { - public const string ProviderName = "providerName"; - public const string ProviderPath = "providerPath"; - public const string ConnectionString = "connectionString"; - public const string ExcludeRoutines = "excludeRoutines"; - public const string ExcludeFKs = "excludeFKs"; - public const string IncludeSchemas = "includeSchemas"; - public const string ExcludeSchemas = "excludeSchemas"; - public const string IncludeCatalogs = "includeCatalogs"; - public const string ExcludeCatalogs = "excludeCatalogs"; - public const string OptimizeJoins = "optimizeJoins"; - public const string UseProviderSpecificTypes = "useProviderSpecificTypes"; - public const string UseCustomFormatter = "useCustomFormatter"; - public const string CommandTimeout = "commandTimeout"; - public const string NormalizeNames = "normalizeNames"; - public const string CustomConfiguration = "customConfiguration"; - } -} diff --git a/Source/Compat/IReadOnlySet.cs b/Source/Compat/IReadOnlySet.cs new file mode 100644 index 0000000..88665ee --- /dev/null +++ b/Source/Compat/IReadOnlySet.cs @@ -0,0 +1,62 @@ +#if !NET5_0_OR_GREATER +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic; + +/// +/// Provides a readonly abstraction of a set. +/// +/// The type of elements in the set. +internal interface IReadOnlySet : IReadOnlyCollection +{ + /// + /// Determines if the set contains a specific item + /// + /// The item to check if the set contains. + /// if found; otherwise . + bool Contains(T item); + /// + /// Determines whether the current set is a proper (strict) subset of a specified collection. + /// + /// The collection to compare to the current set. + /// if the current set is a proper subset of other; otherwise . + /// other is . + bool IsProperSubsetOf(IEnumerable other); + /// + /// Determines whether the current set is a proper (strict) superset of a specified collection. + /// + /// The collection to compare to the current set. + /// if the collection is a proper superset of other; otherwise . + /// other is . + bool IsProperSupersetOf(IEnumerable other); + /// + /// Determine whether the current set is a subset of a specified collection. + /// + /// The collection to compare to the current set. + /// if the current set is a subset of other; otherwise . + /// other is . + bool IsSubsetOf(IEnumerable other); + /// + /// Determine whether the current set is a super set of a specified collection. + /// + /// The collection to compare to the current set + /// if the current set is a subset of other; otherwise . + /// other is . + bool IsSupersetOf(IEnumerable other); + /// + /// Determines whether the current set overlaps with the specified collection. + /// + /// The collection to compare to the current set. + /// if the current set and other share at least one common element; otherwise, . + /// other is . + bool Overlaps(IEnumerable other); + /// + /// Determines whether the current set and the specified collection contain the same elements. + /// + /// The collection to compare to the current set. + /// if the current set is equal to other; otherwise, . + /// other is . + bool SetEquals(IEnumerable other); +} +#endif diff --git a/Source/Compat/ReadOnlyHashSet.cs b/Source/Compat/ReadOnlyHashSet.cs new file mode 100644 index 0000000..fb5a0e2 --- /dev/null +++ b/Source/Compat/ReadOnlyHashSet.cs @@ -0,0 +1,35 @@ +#if !NET5_0_OR_GREATER +namespace System.Collections.Generic; + +internal sealed class ReadOnlyHashSet : IReadOnlySet +{ + private readonly ISet _set; + + public ReadOnlyHashSet(ISet set) + { + _set = set; + } + + int IReadOnlyCollection.Count => _set.Count; + + bool IReadOnlySet.Contains(T item) => _set.Contains(item); + + IEnumerator IEnumerable.GetEnumerator() => _set.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_set).GetEnumerator(); + + bool IReadOnlySet.IsProperSubsetOf(IEnumerable other) => _set.IsProperSubsetOf(other); + + bool IReadOnlySet.IsProperSupersetOf(IEnumerable other) => _set.IsProperSupersetOf(other); + + bool IReadOnlySet.IsSubsetOf(IEnumerable other) => _set.IsSubsetOf(other); + + bool IReadOnlySet.IsSupersetOf(IEnumerable other) => _set.IsSupersetOf(other); + + bool IReadOnlySet.Overlaps(IEnumerable other) => _set.Overlaps(other); + + bool IReadOnlySet.SetEquals(IEnumerable other) => _set.SetEquals(other); + + +} +#endif diff --git a/Source/Compat/ReadOnlySetExtensions.cs b/Source/Compat/ReadOnlySetExtensions.cs new file mode 100644 index 0000000..02b61b0 --- /dev/null +++ b/Source/Compat/ReadOnlySetExtensions.cs @@ -0,0 +1,13 @@ +namespace System.Collections.Generic; + +internal static class ReadOnlySetExtensions +{ + public static IReadOnlySet AsReadOnly(this HashSet set) + { +#if NET5_0_OR_GREATER + return set; +#else + return new ReadOnlyHashSet(set); +#endif + } +} diff --git a/Source/Configuration/AppConfig.cs b/Source/Configuration/AppConfig.cs new file mode 100644 index 0000000..efa9bf6 --- /dev/null +++ b/Source/Configuration/AppConfig.cs @@ -0,0 +1,102 @@ +using System.IO; +using System.Text.Json; +using System.Xml; +using LinqToDB.Configuration; + +namespace LinqToDB.LINQPad; + +/// +/// Implements Linq To DB connection settings provider, which use data from JSON config. +/// Used as settings source for static data context. +/// +internal sealed class AppConfig : ILinqToDBSettings +{ + public static ILinqToDBSettings LoadJson(string configPath) + { + var config = JsonSerializer.Deserialize(File.ReadAllText(configPath)); + + if (config?.ConnectionStrings?.Count is null or 0) + return new AppConfig(Array.Empty()); + + var connections = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + foreach (var cn in config.ConnectionStrings) + { + if (cn.Key.EndsWith("_ProviderName", StringComparison.InvariantCultureIgnoreCase)) + continue; + + connections.Add(cn.Key, new ConnectionStringSettings(cn.Key, cn.Value)); + } + + foreach (var cn in config.ConnectionStrings) + { + if (!cn.Key.EndsWith("_ProviderName", StringComparison.InvariantCultureIgnoreCase)) + continue; + + var key = cn.Key.Substring(0, cn.Key.Length - "_ProviderName".Length); + if (connections.TryGetValue(key, out var cs)) + cs.ProviderName = cn.Value; + } + + return new AppConfig(connections.Values.ToArray()); + } + + public static ILinqToDBSettings LoadAppConfig(string configPath) + { + var xml = new XmlDocument() { XmlResolver = null }; + xml.Load(XmlReader.Create(new StringReader(File.ReadAllText(configPath)), new XmlReaderSettings() { XmlResolver = null })); + + var connections = xml.SelectNodes("/configuration/connectionStrings/add"); + + if (connections?.Count is null or 0) + return new AppConfig(Array.Empty()); + + var settings = new List(); + + foreach (XmlElement node in connections) + { + var name = node.Attributes["name" ]?.Value; + var connectionString = node.Attributes["connectionString"]?.Value; + var providerName = node.Attributes["providerName" ]?.Value; + + if (name != null && connectionString != null) + settings.Add(new ConnectionStringSettings(name, connectionString) { ProviderName = providerName }); + } + + return new AppConfig(settings.ToArray()); + } + + private readonly IConnectionStringSettings[] _connectionStrings; + + public AppConfig(IConnectionStringSettings[] connectionStrings) + { + _connectionStrings = connectionStrings; + } + + IEnumerable ILinqToDBSettings.DataProviders => Array.Empty(); + string? ILinqToDBSettings.DefaultConfiguration => null; + string? ILinqToDBSettings.DefaultDataProvider => null; + IEnumerable ILinqToDBSettings.ConnectionStrings => _connectionStrings; + + private sealed class JsonConfig + { + public IDictionary? ConnectionStrings { get; set; } + } + + private sealed class ConnectionStringSettings : IConnectionStringSettings + { + private readonly string _name; + private readonly string _connectionString; + + public ConnectionStringSettings(string name, string connectionString) + { + _name = name; + _connectionString = connectionString; + } + + string IConnectionStringSettings.ConnectionString => _connectionString; + string IConnectionStringSettings.Name => _name; + bool IConnectionStringSettings.IsGlobal => false; + + public string? ProviderName { get; set; } + } +} diff --git a/Source/Configuration/ConnectionSettings.cs b/Source/Configuration/ConnectionSettings.cs new file mode 100644 index 0000000..806f08d --- /dev/null +++ b/Source/Configuration/ConnectionSettings.cs @@ -0,0 +1,541 @@ +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Xml.Linq; +using LINQPad.Extensibility.DataContext; +using LinqToDB.LINQPad.Json; +using PN = LinqToDB.ProviderName; + +namespace LinqToDB.LINQPad; + +// IMPORTANT: +// settings, marked by [JsonIgnore] stored in default LINQPad connection option properties and must be copied manually on settings save/load +internal sealed class ConnectionSettings +{ + #region Save/Load/Migrate + /// + /// Starting from v5 release we store json string in settings instead of multiple XML nodes to simplify settings management. + /// + private const string SETTINGS_NODE = "SettingsV5"; + + private static readonly JsonSerializerOptions _jsonOptions; + + static ConnectionSettings() + { + _jsonOptions = new() + { + // deserialization options: use permissive options + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + PropertyNameCaseInsensitive = true, + // serialization options + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + // register IReadOnlySet converter factory + _jsonOptions.Converters.Add(IReadOnlySetConverter.Factory); + } + + /// + /// Load connection settings from LINQPad connection object. + /// + public static ConnectionSettings Load(IConnectionInfo cxInfo) + { + ConnectionSettings? settings = null; + + var json = GetString(cxInfo, SETTINGS_NODE); + + if (json != null) + { + settings = JsonSerializer.Deserialize(json, _jsonOptions); + + if (settings != null) + { + settings.Connection ??= new(); + settings.Schema ??= new(); + settings.Scaffold ??= new(); + settings.LinqToDB ??= new(); + settings.StaticContext ??= new(); + } + } + + settings ??= Legacy.Load(cxInfo); + + // load data from predefined IConnectionInfo properties + // Main reason we use predefined properties is to provide connection configuration options to LINQPad so it could use it + // for raw database access functionality + settings.Connection.ConnectionString = cxInfo.DatabaseInfo.CustomCxString; + settings.Connection.Server = cxInfo.DatabaseInfo.Server; + settings.Connection.DatabaseName = cxInfo.DatabaseInfo.Database; + settings.Connection.DbVersion = cxInfo.DatabaseInfo.DbVersion; + settings.Connection.EncryptConnectionString = cxInfo.DatabaseInfo.EncryptCustomCxString; + settings.Connection.ProviderFactory = cxInfo.DatabaseInfo.Provider; + settings.Connection.DisplayName = cxInfo.DisplayName; + settings.Connection.IsProduction = cxInfo.IsProduction; + settings.Connection.Persistent = cxInfo.Persist; + + settings.Scaffold.Pluralize = !cxInfo.DynamicSchemaOptions.NoPluralization; + settings.Scaffold.Capitalize = !cxInfo.DynamicSchemaOptions.NoCapitalization; + + settings.StaticContext.ConfigurationPath = cxInfo.AppConfigPath; + settings.StaticContext.ContextTypeName = cxInfo.CustomTypeInfo.CustomTypeName; + settings.StaticContext.ContextAssemblyPath = cxInfo.CustomTypeInfo.CustomAssemblyPath; + + // manually decrypt secondary connection + if (settings.Connection.EncryptConnectionString && settings.Connection.SecondaryConnectionString != null) + settings.Connection.SecondaryConnectionString = cxInfo.Decrypt(settings.Connection.SecondaryConnectionString); + + return settings; + + // TODO: debug method to reset modifications + //return LoadLegacySettings(cxInfo); + } + + /// + /// Save connection settings to LINQPad connection object. + /// This method should be called from method only. + /// + public void Save(IConnectionInfo cxInfo) + { + // encrypt sencondary connection string manually + if (Connection.EncryptConnectionString && Connection.SecondaryConnectionString != null) + Connection.SecondaryConnectionString = cxInfo.Encrypt(Connection.SecondaryConnectionString); + + // save data, stored in predefined IConnectionInfo properties to them + cxInfo.DatabaseInfo.CustomCxString = Connection.ConnectionString; + cxInfo.DatabaseInfo.Provider = Connection.ProviderFactory; + cxInfo.DatabaseInfo.EncryptCustomCxString = Connection.EncryptConnectionString; + cxInfo.DatabaseInfo.DbVersion = Connection.DbVersion; + cxInfo.DatabaseInfo.Database = Connection.DatabaseName; + cxInfo.DatabaseInfo.Server = Connection.Server; + + cxInfo.DynamicSchemaOptions.NoPluralization = !Scaffold.Pluralize; + cxInfo.DynamicSchemaOptions.NoCapitalization = !Scaffold.Capitalize; + + cxInfo.DisplayName = Connection.DisplayName; + cxInfo.IsProduction = Connection.IsProduction; + cxInfo.Persist = Connection.Persistent; + cxInfo.AppConfigPath = StaticContext.ConfigurationPath; + cxInfo.CustomTypeInfo.CustomTypeName = StaticContext.ContextTypeName; + cxInfo.CustomTypeInfo.CustomAssemblyPath = StaticContext.ContextAssemblyPath; + + var json = JsonSerializer.Serialize(this, _jsonOptions); + SetString(cxInfo, SETTINGS_NODE, json); + } + + /// + /// Legacy options migration support. + /// + private static class Legacy + { + // list item separators for legacy options + private static readonly char[] _listSeparators = new[]{ ',', ';' }; + + // legacy options + private const string ProviderName = "providerName"; + private const string ProviderPath = "providerPath"; + private const string ConnectionString = "connectionString"; + private const string ExcludeRoutines = "excludeRoutines"; + private const string ExcludeFKs = "excludeFKs"; + private const string IncludeSchemas = "includeSchemas"; + private const string ExcludeSchemas = "excludeSchemas"; + private const string IncludeCatalogs = "includeCatalogs"; + private const string ExcludeCatalogs = "excludeCatalogs"; + private const string OptimizeJoins = "optimizeJoins"; + private const string UseProviderSpecificTypes = "useProviderSpecificTypes"; + private const string UseCustomFormatter = "useCustomFormatter"; + private const string CommandTimeout = "commandTimeout"; + private const string NormalizeNames = "normalizeNames"; + private const string CustomConfiguration = "customConfiguration"; + + public static ConnectionSettings Load(IConnectionInfo cxInfo) + { + var settings = new ConnectionSettings(); + settings.Connection = new(); + settings.Schema = new(); + settings.Scaffold = new(); + settings.LinqToDB = new(); + settings.StaticContext = new(); + + // 1. ProviderName migration + + // old provider name option replaced with two options: database and database provider + settings.Connection.Provider = GetString(cxInfo, ProviderName); + + // this native oracle provider was removed long time ago and not supported in v5 too + if (settings.Connection.Provider == PN.OracleNative) + settings.Connection.Provider = PN.OracleManaged; + + // switch contains only provider names, used by pre-v5 driver + settings.Connection.Database = settings.Connection.Provider switch + { + + PN.AccessOdbc => PN.Access, + PN.MySqlConnector => PN.MySql, + PN.SybaseManaged => PN.Sybase, + PN.SQLiteClassic => PN.SQLite, + PN.InformixDB2 => PN.Informix, + PN.SapHanaNative + or PN.SapHanaOdbc => PN.SapHana, + PN.OracleManaged => PN.Oracle, + // preserve same name + PN.Firebird + or PN.Access + or PN.PostgreSQL + or PN.DB2LUW + or PN.DB2zOS + or PN.SqlServer + //or DB2iSeriesProviderName.DB2 + or PN.SqlCe => settings.Connection.Provider, + _ => null + }; + + // 2. IncludeSchemas, ExcludeSchemas, IncludeCatalogs and ExcludeCatalogs migration + + // 2. convert comma/semicolon-separated strings with schemas/catalogs to list + flag + var strValue = GetString(cxInfo, ExcludeSchemas); + var schemas = strValue == null ? null : new HashSet(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries)); + if (schemas != null && schemas.Count > 0) + settings.Schema.IncludeSchemas = false; + else + { + strValue = GetString(cxInfo, IncludeSchemas); + schemas = strValue == null ? null : new HashSet(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries)); + if (schemas != null && schemas.Count > 0) + settings.Schema.IncludeSchemas = true; + } + + settings.Schema.Schemas = schemas?.AsReadOnly(); + + strValue = GetString(cxInfo, ExcludeCatalogs); + var catalogs = strValue == null ? null : new HashSet(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries)); + if (catalogs != null && catalogs.Count > 0) + settings.Schema.IncludeCatalogs = false; + else + { + strValue = GetString(cxInfo, IncludeCatalogs); + catalogs = strValue == null ? null : new HashSet(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries)); + if (catalogs != null && catalogs.Count > 0) + settings.Schema.IncludeCatalogs = true; + } + settings.Schema.Catalogs = catalogs?.AsReadOnly(); + + // 3. ExcludeRoutines migration + + settings.Schema.LoadAggregateFunctions + = settings.Schema.LoadScalarFunctions + = settings.Schema.LoadTableFunctions + = settings.Schema.LoadProcedures + = !GetBoolean(cxInfo, ExcludeRoutines, true).Value; + + // 4. ExcludeFKs migration + settings.Schema.LoadForeignKeys = !GetBoolean(cxInfo, ExcludeFKs, false).Value; + + // 5. ProviderPath migration + settings.Connection.ProviderPath = GetString(cxInfo, ProviderPath); + + // 6. CommandTimeout migration + // note that in pre-v5 it was non-nullable option so it wasn't possible to use default db/provider timeout + settings.Connection.CommandTimeout = GetInt32(cxInfo, CommandTimeout); + + // 7. ConnectionString migration + // note that in practice pre-v4 never stored connection in custom field and used CustomCxString as storage + settings.Connection.ConnectionString = GetString(cxInfo, ConnectionString); + if (!string.IsNullOrWhiteSpace(settings.Connection.ConnectionString)) + cxInfo.DatabaseInfo.CustomCxString = settings.Connection.ConnectionString; + + // 8. OptimizeJoins migration + settings.LinqToDB.OptimizeJoins = GetBoolean(cxInfo, OptimizeJoins, true).Value; + + // 9. UseProviderSpecificTypes migration + settings.Scaffold.UseProviderTypes = GetBoolean(cxInfo, UseProviderSpecificTypes, false).Value; + + // 10. CustomConfiguration migration + settings.StaticContext.ConfigurationName = GetString(cxInfo, CustomConfiguration); + + // ignored options: + // UseCustomFormatter - removed in v5 + // NormalizeNames - not used in pre-v5 and v5 (never used?) + + return settings; + } + } + + [return: NotNullIfNotNull(nameof(defaultValue))] + private static int? GetInt32(IConnectionInfo cxInfo, XName name, int? defaultValue = null) + { + var strValue = GetString(cxInfo, name); + + if (strValue != null && int.TryParse(strValue, NumberStyles.None, CultureInfo.InvariantCulture, out var intValue)) + return intValue; + + return defaultValue; + } + + [return: NotNullIfNotNull(nameof(defaultValue))] + private static bool? GetBoolean(IConnectionInfo cxInfo, XName name, bool? defaultValue = null) + { + var strValue = GetString(cxInfo, name); + return strValue == "true" ? true : strValue == "false" ? false : defaultValue; + } + + [return: NotNullIfNotNull(nameof(defaultValue))] + private static string? GetString(IConnectionInfo cxInfo, XName name, string? defaultValue = null) => cxInfo.DriverData.Element(name)?.Value ?? defaultValue; + + private static void SetString(IConnectionInfo cxInfo, XName name, string? value) + { + if (value != null) + cxInfo.DriverData.SetElementValue(name, value); + else + cxInfo.DriverData.Element(name)?.Remove(); + } + #endregion + + public ConnectionOptions Connection { get; set; } = null!; + public SchemaOptions Schema { get; set; } = null!; + public ScaffoldOptions Scaffold { get; set; } = null!; + public LinqToDbOptions LinqToDB { get; set; } = null!; + public StaticContextOptions StaticContext { get; set; } = null!; + + public sealed class ConnectionOptions + { + /// + /// Database identifier. Usually generic name from . + /// + public string? Database { get; set; } + + /// + /// Database provider identifier. Specific name from . + /// + public string? Provider { get; set; } + + /// + /// Database provider assembly path. + /// + [JsonIgnore] + public string? ProviderPath + { + get => IntPtr.Size == 4 ? ProviderPathx86 ?? ProviderPathx64 : ProviderPathx64 ?? ProviderPathx86; + set + { + if (IntPtr.Size == 4) + ProviderPathx86 = value; + else + ProviderPathx64 = value; + } + } + + /// + /// Database provider assembly path. + /// + public string? ProviderPathx86 { get; set; } + + /// + /// Database provider assembly path. + /// + public string? ProviderPathx64 { get; set; } + + /// + /// Command timeout. null for provider/database default timeout. + /// + public int? CommandTimeout { get; set; } + + /// + /// Database provider name for secondary schema connection. + /// + public string? SecondaryProvider { get; set; } + + /// + /// Secondary schema connection string. + /// + public string? SecondaryConnectionString { get; set; } + + /// + /// User-defined connection name. + /// Stored in . + /// + [JsonIgnore] + public string? DisplayName { get; set; } + + /// + /// Marks connected database as containing production data. + /// Stored in . + /// + [JsonIgnore] + public bool IsProduction { get; set; } + + /// + /// Marks connection as persistent (saved before restarts). + /// Stored in . + /// + [JsonIgnore] + public bool Persistent { get; set; } + + /// + /// Connection string. Stored in . + /// + [JsonIgnore] + public string? ConnectionString { get; set; } + + /// + /// Stored in . + /// + [JsonIgnore] + public string? ProviderFactory { get; set; } + + /// + /// Database information (). + /// Stored in . + /// + [JsonIgnore] + public string? Server { get; set; } + + /// + /// Database information (). + /// Stored in . + /// + [JsonIgnore] + public string? DatabaseName { get; set; } + + /// + /// Database information (). + /// Stored in . + /// + [JsonIgnore] + public string? DbVersion { get; set; } + + /// + /// Instructs LINQPad to encrypt value. + /// Also instruct us to encrypt value. + /// Stored in . + /// + [JsonIgnore] + public bool EncryptConnectionString { get; set; } + } + + public sealed class SchemaOptions + { + /// + /// Include/exclude schemas, specified by option. + /// + public bool IncludeSchemas { get; set; } + + /// + /// List of schemas to include/exclude (defined by option). + /// + public IReadOnlySet? Schemas { get; set; } + + /// + /// Include/exclude catalogs, specified by option. + /// + public bool IncludeCatalogs { get; set; } + + /// + /// List of catalogs to include/exclude (defined by option). + /// + public IReadOnlySet? Catalogs { get; set; } + + /// + /// Populate stored procedures. + /// + public bool LoadProcedures { get; set; } + + /// + /// Populate table functions. + /// + public bool LoadTableFunctions { get; set; } + + /// + /// Populate scalar functions. + /// + public bool LoadScalarFunctions { get; set; } + + /// + /// Populate aggregate functions. + /// + public bool LoadAggregateFunctions { get; set; } + + /// + /// Populate foreign keys. + /// + public bool LoadForeignKeys { get; set; } + } + + public sealed class ScaffoldOptions + { + /// + /// Use provider data types. + /// + public bool UseProviderTypes { get; set; } + + /// + /// Map FixedString(X) to for ClickHouse. + /// + public bool ClickHouseFixedStringAsString { get; set; } + + /// + /// Enables pluralization context table property name and collection-type association property name. + /// Stored in . + /// + [JsonIgnore] + public bool Pluralize { get; set; } + + /// + /// Enables capitalization of table column properties. + /// Stored in . + /// + [JsonIgnore] + public bool Capitalize { get; set; } + } + + public sealed class LinqToDbOptions + { + /// + /// Value for Linq To DB setting. + /// + public bool OptimizeJoins { get; set; } + } + + public sealed class StaticContextOptions + { + /// + /// Name of custom configuration (connection string name), passed to context constructor. + /// + public string? ConfigurationName { get; set; } + + /// + /// Path to custom configuration file. + /// For LINQPad 5 it should be in app.config format, for .NET Core versions - in appsettings.json format. + /// Stored in . + /// + [JsonIgnore] // strored in linqpad storage + public string? ConfigurationPath { get; set; } + +#if NETFRAMEWORK + /// + /// Path to appsettings.json configuration file for LINQPad 5. + /// We cannot store it in as LINQPad will try to use + /// it as app.config file and fail. + /// + public string? LocalConfigurationPath { get; set; } +#endif + + /// + /// Full name of data context class (namespace + class name) in custom context assembly. + /// Stored in . + /// + [JsonIgnore] + public string? ContextTypeName { get; set; } + + /// + /// Full path to custom context assembly. + /// Stored in . + /// + [JsonIgnore] + public string? ContextAssemblyPath { get; set; } + } +} diff --git a/Source/Configuration/CustomSerializers/IReadOnlySetConverter.cs b/Source/Configuration/CustomSerializers/IReadOnlySetConverter.cs new file mode 100644 index 0000000..0aa590f --- /dev/null +++ b/Source/Configuration/CustomSerializers/IReadOnlySetConverter.cs @@ -0,0 +1,75 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace LinqToDB.LINQPad.Json; + +internal sealed class IReadOnlySetConverter : JsonConverter> +{ + private readonly JsonConverter _elementConverter; + private readonly Type _elementType = typeof(T); + + private static IReadOnlySetConverter? _instance; + + public static readonly JsonConverterFactory Factory = new IReadOnlySetConverterFactory(); + + private static JsonConverter GetInstance(JsonConverter elementConverter) + { + return _instance ??= new IReadOnlySetConverter(elementConverter); + } + + private IReadOnlySetConverter(JsonConverter elementConverter) + { + _elementConverter = elementConverter; + } + + public override IReadOnlySet Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var hashSet = new HashSet(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + var item = _elementConverter.Read(ref reader, _elementType, options); + hashSet.Add(item!); + } + + return hashSet.AsReadOnly(); + } + + public override void Write(Utf8JsonWriter writer, IReadOnlySet value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var item in value) + { + _elementConverter.Write(writer, item, options); + } + writer.WriteEndArray(); + } + + private sealed class IReadOnlySetConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + return typeToConvert.GetGenericTypeDefinition() == typeof(IReadOnlySet<>); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var elementType = typeToConvert.GetGenericArguments()[0]; + + var converterType = typeof(IReadOnlySetConverter<>).MakeGenericType(elementType); + return (JsonConverter)converterType + .GetMethod(nameof(IReadOnlySetConverter.GetInstance), BindingFlags.NonPublic | BindingFlags.Static)! + .Invoke(null, new[] { options.GetConverter(elementType) })!; + } + } +} diff --git a/Source/ConnectionDialog.xaml b/Source/ConnectionDialog.xaml deleted file mode 100644 index ce2850b..0000000 --- a/Source/ConnectionDialog.xaml +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -