diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/FreeSql.Tests.Provider.TDengine.csproj b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/FreeSql.Tests.Provider.TDengine.csproj new file mode 100644 index 000000000..8b5797ec9 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/FreeSql.Tests.Provider.TDengine.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/UnitTest1.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/UnitTest1.cs new file mode 100644 index 000000000..69995c959 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/UnitTest1.cs @@ -0,0 +1,16 @@ +namespace FreeSql.Tests.Provider.TDengine +{ + public class Tests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/FreeSql.sln b/FreeSql.sln index d0cb3099f..f1f4a392c 100644 --- a/FreeSql.sln +++ b/FreeSql.sln @@ -133,6 +133,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.Provider.Duck EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeSql.Provider.TDengine", "Providers\FreeSql.Provider.TDengine\FreeSql.Provider.TDengine.csproj", "{329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeSql.Tests.Provider.TDengine", "FreeSql.Tests\FreeSql.Tests.Provider.TDengine\FreeSql.Tests.Provider.TDengine.csproj", "{1F313BE0-5069-4B5E-BEE7-138954D293F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -803,6 +805,18 @@ Global {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|x64.Build.0 = Release|Any CPU {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|x86.ActiveCfg = Release|Any CPU {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|x86.Build.0 = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|x64.Build.0 = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Debug|x86.Build.0 = Debug|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|Any CPU.Build.0 = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|x64.ActiveCfg = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|x64.Build.0 = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|x86.ActiveCfg = Release|Any CPU + {1F313BE0-5069-4B5E-BEE7-138954D293F9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -848,8 +862,8 @@ Global {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317} = {2A381C57-2697-427B-9F10-55DA11FD02E4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - RESX_NeutralResourcesLanguage = en-US - RESX_PrefixTranslations = True SolutionGuid = {089687FD-5D25-40AB-BA8A-A10D1E137F98} + RESX_PrefixTranslations = True + RESX_NeutralResourcesLanguage = en-US EndGlobalSection EndGlobal diff --git a/FreeSql/DataType.cs b/FreeSql/DataType.cs index 0409f7062..5fe899ee2 100644 --- a/FreeSql/DataType.cs +++ b/FreeSql/DataType.cs @@ -65,6 +65,8 @@ namespace FreeSql CustomOracle, CustomSqlServer, CustomMySql, CustomPostgreSQL, - DuckDB + DuckDB, + + TDengine } } diff --git a/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSTableAttribute.cs b/Providers/FreeSql.Provider.TDengine/Attributes/STableAttribute.cs similarity index 83% rename from Providers/FreeSql.Provider.TDengine/Attributes/TDengineSTableAttribute.cs rename to Providers/FreeSql.Provider.TDengine/Attributes/STableAttribute.cs index 9b5370751..3c33c5de2 100644 --- a/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSTableAttribute.cs +++ b/Providers/FreeSql.Provider.TDengine/Attributes/STableAttribute.cs @@ -10,7 +10,7 @@ namespace FreeSql.DataAnnotations /// TDengine 超级表 /// [AttributeUsage(AttributeTargets.Class)] - public class TDengineSTableAttribute : Attribute + public class STableAttribute : Attribute { } } \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs new file mode 100644 index 000000000..3cd2ae701 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.Internal.Model; +using FreeSql.Internal.ObjectPool; + +namespace FreeSql.Provider.TDengine.TDengineAdo +{ + internal class TDengineAdo : AdoProvider + { + public TDengineAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, Func connectionFactory) : base(DataType.TDengine, masterConnectionString, slaveConnectionStrings) + { + + } + + public override object AddslashesProcessParam(object param, Type mapType, ColumnInfo mapColumn) + { + throw new NotImplementedException(); + } + + public override DbCommand CreateCommand() + { + throw new NotImplementedException(); + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) + { + throw new NotImplementedException(); + } + + public override void ReturnConnection(IObjectPool pool, Object conn, Exception ex) + { + throw new NotImplementedException(); + } + } +} diff --git a/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs new file mode 100644 index 000000000..c4cb96ac1 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using FreeSql.Internal.ObjectPool; +using TDengine.Data.Client; + +namespace FreeSql.Provider.TDengine.TDengineAdo +{ + internal class TDengineConnectionPool : ObjectPool + { + internal Action AvailableHandler; + + internal Action UnavailableHandler; + + + public TDengineConnectionPool(string name, string connectionString, Action availableHandler, + Action unavailableHandler) : base(null) + { + this.AvailableHandler = availableHandler; + this.UnavailableHandler = unavailableHandler; + var policy = new TDengineConnectionPoolPolicy + { + InternalPool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + } + } + + internal class TDengineConnectionPoolPolicy : IPolicy + { + internal TDengineConnectionPool InternalPool; + public string Name { get; set; } = $"TDengine Connection {CoreStrings.S_ObjectPool}"; + public int PoolSize { get; set; } = 50; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromSeconds(20); + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public bool IsAutoDisposeWithSystem { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 2; + public int Weight { get; set; } = 1; + + static readonly ConcurrentDictionary DicConnStrIncr = + new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + + private string _connectionString; + + public string ConnectionString + { + get => _connectionString; + set + { + _connectionString = value ?? ""; + + var minPoolSize = 0; + var pattern = @"Min(imum)?\s*pool\s*size\s*=\s*(\d+)"; + var m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + minPoolSize = int.Parse(m.Groups[2].Value); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + pattern = @"Max(imum)?\s*pool\s*size\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[2].Value, out var poolsize) == false || poolsize <= 0) + poolsize = Math.Max(50, minPoolSize); + var connStrIncr = + DicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => Math.Min(5, oldval + 1)); + PoolSize = poolsize + connStrIncr; + _connectionString = m.Success + ? Regex.Replace(_connectionString, pattern, $"Maximum pool size={PoolSize}", + RegexOptions.IgnoreCase) + : $"{_connectionString};Maximum pool size={PoolSize}"; + + pattern = @"Connection\s*LifeTime\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + IdleTimeout = TimeSpan.FromSeconds(int.Parse(m.Groups[1].Value)); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(InternalPool, minPoolSize); + } + } + + public DbConnection OnCreate() + { + var conn = new TDengineConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) + { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGetTimeout() + { + + } + + public void OnGet(Object obj) + { + if (InternalPool.IsAvailable) + { + if (obj.Value == null) + { + InternalPool.SetUnavailable(new Exception(CoreStrings.S_ConnectionStringError), obj.LastGetTimeCopy); + throw new Exception(CoreStrings.S_ConnectionStringError_Check(this.Name)); + } + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) + { + + try + { + obj.Value.Open(); + } + catch (Exception ex) + { + if (InternalPool.SetUnavailable(ex, obj.LastGetTimeCopy) == true) + throw new Exception($"【{this.Name}】Block access and wait for recovery: {ex.Message}"); + throw ex; + } + } + } + } + +#if net40 +#else + public async Task OnGetAsync(Object obj) + { + if (InternalPool.IsAvailable) + { + if (obj.Value == null) + { + InternalPool.SetUnavailable(new Exception(CoreStrings.S_ConnectionStringError), obj.LastGetTimeCopy); + throw new Exception(CoreStrings.S_ConnectionStringError_Check(this.Name)); + } + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (await obj.Value.PingAsync()) == false) + { + + try + { + await obj.Value.OpenAsync(); + } + catch (Exception ex) + { + if (InternalPool.SetUnavailable(ex, obj.LastGetTimeCopy) == true) + throw new Exception($"【{this.Name}】Block access and wait for recovery: {ex.Message}"); + throw ex; + } + } + } + } +#endif + public void OnReturn(Object obj) + { + } + + public bool OnCheckAvailable(Object obj) + { + if (obj.Value == null) return false; + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + return obj.Value.Ping(true); + } + + public void OnAvailable() + { + InternalPool.AvailableHandler?.Invoke(); + } + + public void OnUnavailable() + { + InternalPool.UnavailableHandler?.Invoke(); + } + } + + static class DbConnectionExtensions + { + static DbCommand PingCommand(DbConnection conn) + { + var cmd = conn.CreateCommand(); + cmd.CommandTimeout = 5; + cmd.CommandText = "select 1"; + return cmd; + } + + public static bool Ping(this DbConnection that, bool isThrow = false) + { + try + { + PingCommand(that).ExecuteNonQuery(); + return true; + } + catch + { + if (that.State != ConnectionState.Closed) + try + { + that.Close(); + } + catch + { + // ignored + } + + if (isThrow) throw; + return false; + } + } + +#if net40 +#else + public static async Task PingAsync(this DbConnection that, bool isThrow = false) + { + try + { + await PingCommand(that).ExecuteNonQueryAsync(); + return true; + } + catch + { + if (that.State != ConnectionState.Closed) + try + { + that.Close(); + } + catch + { + } + + if (isThrow) throw; + return false; + } + } +#endif + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineCodeFirst.cs b/Providers/FreeSql.Provider.TDengine/TDengineCodeFirst.cs new file mode 100644 index 000000000..c50111a3f --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineCodeFirst.cs @@ -0,0 +1,28 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.Provider.TDengine +{ + internal class TDengineCodeFirst : Internal.CommonProvider.CodeFirstProvider + { + public TDengineCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) + { + + } + + public override DbInfoResult GetDbInfo(Type type) + { + throw new NotImplementedException(); + } + + protected override string GetComparisonDDLStatements(params TypeSchemaAndName[] objects) + { + throw new NotImplementedException(); + } + } +} diff --git a/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs b/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs new file mode 100644 index 000000000..f69f10d26 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs @@ -0,0 +1,7 @@ +namespace FreeSql.Provider.TDengine +{ + public class TDengineDbFirst + { + + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineProvider.cs b/Providers/FreeSql.Provider.TDengine/TDengineProvider.cs new file mode 100644 index 000000000..cc48bb540 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FreeSql.Internal.CommonProvider; + +namespace FreeSql.Provider.TDengine +{ + internal class TDengineProvider : BaseDbProvider, IFreeSql + { + public override ISelect CreateSelectProvider(object dywhere) + { + throw new NotImplementedException(); + } + + public override IInsert CreateInsertProvider() + { + throw new NotImplementedException(); + } + + public override IUpdate CreateUpdateProvider(object dywhere) + { + throw new NotImplementedException(); + } + + public override IDelete CreateDeleteProvider(object dywhere) + { + throw new NotImplementedException(); + } + + public override IInsertOrUpdate CreateInsertOrUpdateProvider() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file