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..8769da46d --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/FreeSql.Tests.Provider.TDengine.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/TDengineAdo/TDengineAdoTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/TDengineAdo/TDengineAdoTest.cs new file mode 100644 index 000000000..065e2d146 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/TDengineAdo/TDengineAdoTest.cs @@ -0,0 +1,104 @@ +using System; +using FreeSql.Tests.Provider.TDengine.TDengine.Tables; +using Xunit; + +namespace FreeSql.Tests.Provider.TDengine.TDengine.TDengineAdo +{ + public class TDengineAdoTest + { + IFreeSql fsql => g.tdengine; + + [Fact] + void CodeFirstTest() + { + fsql.CodeFirst.SyncStructure(); + fsql.CodeFirst.SyncStructure(); + fsql.CodeFirst.SyncStructure(); + } + + [Fact] + void InsertTest() + { + var insertAffrows = fsql.Insert(new D1001() + { + Ts = DateTime.Now, + Current = 1, + Voltage = 1, + Describe = "D10021" + } + ).ExecuteAffrows(); + + var insertAffrows2 = fsql.Insert(new D1001() + { + Ts = DateTime.Now, + Current = 1, + Voltage = 1, + Describe = "D10021" + } + ).ExecuteAffrows(); + + var batchInsertAffrows = fsql.Insert(new List() + { + new D1002() + { + Ts = DateTime.Now, + Current = 6, + Voltage = 6, + Describe = "D10026" + }, + new D1002() + { + Ts = DateTime.Now, + Current = 3, + Voltage = 3, + Describe = "D10023" + }, + new D1002() + { + Ts = DateTime.Now, + Current = 4, + Voltage = 4, + Describe = "D10024" + } + } + ).ExecuteAffrows(); + } + + [Fact] + void SelectTest() + { + var subList = fsql.Select().ToList(d => new + { + GroupId = d.GroupId + }); + + var superMetersList = fsql.Select().ToList(); + } + + [Fact] + void WhereSelectTest() + { + var list = fsql.Select().Where(d => d.GroupId == 2).ToList(); + } + + [Fact] + void DeleteTest() + { + var startTime = DateTime.Parse("2024-11-30T02:33:52.308+00:00"); + var endTime = DateTime.Parse("2024-11-30T02:40:58.961+00:00"); + var executeAffrows = fsql.Delete() + .Where(meters => meters.Ts >= startTime && meters.Ts <= endTime && meters.GroupId == 1) + .ExecuteAffrows(); + } + + [Fact] + void DbFirst_GetDatabases() + { + var databases = fsql.DbFirst.GetDatabases(); + foreach (var database in databases) + { + Console.WriteLine(database); + } + } + } +} \ No newline at end of file diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Meters.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Meters.cs new file mode 100644 index 000000000..f932ab5e6 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Meters.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FreeSql.DataAnnotations; +using FreeSql.Provider.TDengine.Attributes; + +namespace FreeSql.Tests.Provider.TDengine.TDengine.Tables +{ + [TDengineSuperTable(Name = "meters")] + public class Meters + { + [Column(Name = "ts")] + public DateTime Ts { get; set; } + + [Column(Name = "current")] + public float Current { get; set; } + + [Column(Name = "voltage")] + public int Voltage { get; set; } + + [Column(Name = "describe", StringLength = 50)] + public string? Describe { get; set; } + + [TDengineTag(Name = "location")] + public virtual string? Location { get; set; } + + [TDengineTag(Name = "group_id")] + public virtual int GroupId { get; set; } + } + + [TDengineSubTable(SuperTableName = "meters", Name = "d1001")] + public class D1001 : Meters + { + [TDengineTag(Name = "location")] + public override string Location { get; set; } = "BeiJIng.ChaoYang"; + + [TDengineTag(Name = "group_id")] + public override int GroupId { get; set; } = 1; + } + + [TDengineSubTable(SuperTableName = "meters", Name = "d1002")] + public class D1002 : Meters + { + [TDengineTag(Name = "location")] + public new string Location { get; set; } = "California.SanFrancisco"; + + [TDengineTag(Name = "group_id")] + public new int GroupId { get; set; } = 2; + } +} \ No newline at end of file diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Users.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Users.cs new file mode 100644 index 000000000..9d3fea7a5 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengine/Tables/Users.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FreeSql.DataAnnotations; + +namespace FreeSql.Tests.Provider.TDengine.TDengine.Tables +{ + [Table(Name = "users")] + public class Users + { + [Column(Name = "ts")] + public DateTime Ts { get; set; } + + [Column(Name = "id")] + public float Id { get; set; } + + [Column(Name = "address")] + public int Address { get; set; } + + [Column(Name = "name", StringLength = 20)] + public string? Name { get; set; } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengineAdo/TDengineAdoTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengineAdo/TDengineAdoTest.cs new file mode 100644 index 000000000..8fcd64b11 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/TDengineAdo/TDengineAdoTest.cs @@ -0,0 +1,14 @@ +namespace FreeSql.Tests.Provider.TDengine.TDengineAdo +{ + public class TDengineAdoTest + { + IFreeSql fsql => g.tdengine; + + [Fact] + public void AuditValue() + { + var executeConnectTest = fsql.Ado.ExecuteConnectTest(); + + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/g.cs b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/g.cs new file mode 100644 index 000000000..e8d31270c --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.Provider.TDengine/g.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.Tests.Provider.TDengine +{ + internal class g + { + private static readonly Lazy tdengineLazy = new Lazy(() => + { + var fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.TDengine, + "host=localhost;port=6030;username=root;password=taosdata;protocol=Native;db=test;") + .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}\r\n")) + .UseNoneCommandParameter(true) + .Build(); + return fsql; + }); + + public static IFreeSql tdengine => tdengineLazy.Value; + } +} \ No newline at end of file diff --git a/FreeSql.sln b/FreeSql.sln index b556b639e..ee33927be 100644 --- a/FreeSql.sln +++ b/FreeSql.sln @@ -131,6 +131,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Provider.Duckdb", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.Provider.Duckdb", "FreeSql.Tests\FreeSql.Tests.Provider.Duckdb\FreeSql.Tests.Provider.Duckdb.csproj", "{DE79C012-15B1-40EC-AD19-CBD7D489DB8E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Provider.TDengine", "Providers\FreeSql.Provider.TDengine\FreeSql.Provider.TDengine.csproj", "{329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.Provider.TDengine", "FreeSql.Tests\FreeSql.Tests.Provider.TDengine\FreeSql.Tests.Provider.TDengine.csproj", "{0C178F1C-0F65-47AA-A1DE-545CBF8543D1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -789,6 +793,30 @@ Global {DE79C012-15B1-40EC-AD19-CBD7D489DB8E}.Release|x64.Build.0 = Release|Any CPU {DE79C012-15B1-40EC-AD19-CBD7D489DB8E}.Release|x86.ActiveCfg = Release|Any CPU {DE79C012-15B1-40EC-AD19-CBD7D489DB8E}.Release|x86.Build.0 = Release|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|Any CPU.Build.0 = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|x64.ActiveCfg = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|x64.Build.0 = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|x86.ActiveCfg = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Debug|x86.Build.0 = Debug|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|Any CPU.ActiveCfg = Release|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|Any CPU.Build.0 = Release|Any CPU + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317}.Release|x64.ActiveCfg = Release|Any CPU + {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 + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|x64.Build.0 = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Debug|x86.Build.0 = Debug|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|Any CPU.Build.0 = Release|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|x64.ActiveCfg = Release|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|x64.Build.0 = Release|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|x86.ActiveCfg = Release|Any CPU + {0C178F1C-0F65-47AA-A1DE-545CBF8543D1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -831,6 +859,7 @@ Global {8064870C-22EA-4A58-972D-DBD57D096D91} = {2A381C57-2697-427B-9F10-55DA11FD02E4} {D9419896-BFB0-47C1-BEFD-A6C48394643B} = {4A92E8A6-9A6D-41A1-9CDA-DE10899648AA} {4871434E-481D-4306-B6DD-73595C61A473} = {2A381C57-2697-427B-9F10-55DA11FD02E4} + {329BA8B3-4139-4CCE-AFEC-4BE9B7BED317} = {2A381C57-2697-427B-9F10-55DA11FD02E4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution RESX_NeutralResourcesLanguage = en-US 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 @@ public enum DataType { CustomOracle, CustomSqlServer, CustomMySql, CustomPostgreSQL, - DuckDB + DuckDB, + + TDengine } } diff --git a/FreeSql/FreeSqlBuilder.cs b/FreeSql/FreeSqlBuilder.cs index 6f5afd90a..90f626dcb 100644 --- a/FreeSql/FreeSqlBuilder.cs +++ b/FreeSql/FreeSqlBuilder.cs @@ -385,6 +385,11 @@ public IFreeSql Build() if (type == null) throwNotFind("FreeSql.Provider.Duckdb.dll", "FreeSql.Duckdb.DuckdbProvider<>"); break; + case DataType.TDengine: + type = Type.GetType("FreeSql.TDengine.TDengineProvider`1,FreeSql.Provider.TDengine")?.MakeGenericType(typeof(TMark)); + if (type == null) throwNotFind("FreeSql.Provider.TDengine.dll", "FreeSql.TDengine.TDengineProvider<>"); + break; + default: throw new Exception(CoreErrorStrings.NotSpecified_UseConnectionString_UseConnectionFactory); } } diff --git a/FreeSql/Internal/CommonProvider/InsertProvider.cs b/FreeSql/Internal/CommonProvider/InsertProvider.cs index c099faa59..faca3c67f 100644 --- a/FreeSql/Internal/CommonProvider/InsertProvider.cs +++ b/FreeSql/Internal/CommonProvider/InsertProvider.cs @@ -609,10 +609,10 @@ public IInsert AsType(Type entityType) public virtual string ToSql() => ToSqlValuesOrSelectUnionAllExtension103(true, null, null, false); - public string ToSqlValuesOrSelectUnionAll(bool isValues = true) => ToSqlValuesOrSelectUnionAllExtension103(isValues, null, null, false); + public string ToSqlValuesOrSelectUnionAll(bool isValues = true, List ignoreColumn = null) => ToSqlValuesOrSelectUnionAllExtension103(isValues, null, null, false, ignoreColumn); public string ToSqlValuesOrSelectUnionAllExtension101(bool isValues, Action onrow) => ToSqlValuesOrSelectUnionAllExtension103(isValues, null, onrow, false); public string ToSqlValuesOrSelectUnionAllExtension102(bool isValues, Action onrowPre, Action onrow) => ToSqlValuesOrSelectUnionAllExtension103(isValues, onrowPre, onrow, false); - string ToSqlValuesOrSelectUnionAllExtension103(bool isValues, Action onrowPre, Action onrow, bool isAsTableSplited) + string ToSqlValuesOrSelectUnionAllExtension103(bool isValues, Action onrowPre, Action onrow, bool isAsTableSplited, List ignoreColumn = null) { if (_source == null || _source.Any() == false) return null; var sb = new StringBuilder(); @@ -643,6 +643,8 @@ string ToSqlValuesOrSelectUnionAllExtension103(bool isValues, Action replaceCounter++ == 0 ? $"{m.Groups[1].Value}({strlen})" : m.Groups[0].Value); break; + case DataType.TDengine: + colattr.DbType = Regex.Replace(colattr.DbType, charPattern, m => + replaceCounter++ == 0 ? $"{m.Groups[1].Value}({strlen})" : m.Groups[0].Value); + break; case DataType.MsAccess: charPattern = @"(CHAR|CHAR2|CHARACTER|TEXT)\s*(\([^\)]*\))?"; if (strlen < 0) colattr.DbType = $"LONGTEXT{strNotNull}"; diff --git a/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSubTableAttribute.cs b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSubTableAttribute.cs new file mode 100644 index 000000000..4264d0a7e --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSubTableAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.DataAnnotations +{ + /// + /// TDengine 超级表-子表 + /// + [AttributeUsage(AttributeTargets.Class)] + public class TDengineSubTableAttribute : TableAttribute + { + /// + /// 超表名称 + /// + public string SuperTableName { get; set; } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSuperTableAttribute.cs b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSuperTableAttribute.cs new file mode 100644 index 000000000..a063cf500 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineSuperTableAttribute.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.DataAnnotations +{ + /// + /// TDengine 超级表 + /// + [AttributeUsage(AttributeTargets.Class)] + public class TDengineSuperTableAttribute : TableAttribute + { + + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/Attributes/TDengineTagAttribute.cs b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineTagAttribute.cs new file mode 100644 index 000000000..9d4c7ae54 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Attributes/TDengineTagAttribute.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FreeSql.DataAnnotations; + +namespace FreeSql.Provider.TDengine.Attributes +{ + [AttributeUsage(AttributeTargets.Property)] + public class TDengineTagAttribute : ColumnAttribute + { + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/Curd/TDengineDelete.cs b/Providers/FreeSql.Provider.TDengine/Curd/TDengineDelete.cs new file mode 100644 index 000000000..b1acc558c --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Curd/TDengineDelete.cs @@ -0,0 +1,26 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.TDengine.Curd +{ + + class TDengineDelete : Internal.CommonProvider.DeleteProvider + { + public TDengineDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override List ExecuteDeleted() => throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + +#if net40 +#else + public override Task> ExecuteDeletedAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); +#endif + } +} diff --git a/Providers/FreeSql.Provider.TDengine/Curd/TDengineInsert.cs b/Providers/FreeSql.Provider.TDengine/Curd/TDengineInsert.cs new file mode 100644 index 000000000..cefc23e91 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Curd/TDengineInsert.cs @@ -0,0 +1,228 @@ +using FreeSql.DataAnnotations; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using FreeSql.Provider.TDengine.Attributes; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.TDengine.Curd +{ + class TDengineInsert : Internal.CommonProvider.InsertProvider where T1 : class + { + public TDengineInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) + { + } + + internal bool InternalIsIgnoreInto = false; + internal IFreeSql InternalOrm => _orm; + internal TableInfo InternalTable => _table; + internal DbParameter[] InternalParams => _params; + internal DbConnection InternalConnection => _connection; + internal DbTransaction InternalTransaction => _transaction; + internal CommonUtils InternalCommonUtils => _commonUtils; + internal CommonExpression InternalCommonExpression => _commonExpression; + internal List InternalSource => _source; + internal Dictionary InternalIgnore => _ignore; + internal void InternalClearData() => ClearData(); + + public override int ExecuteAffrows() => base.SplitExecuteAffrows( + _batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + + public override long ExecuteIdentity() => base.SplitExecuteIdentity( + _batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + + public override List ExecuteInserted() => base.SplitExecuteInserted( + _batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + + + public override string ToSql() + { + //处理Insert忽略Tag + var ignoreColumnList = _ignoreInsertColumns.GetOrAdd(typeof(T1), s => + { + //如果是超表不处理 + if (!s.IsDefined(typeof(TDengineSubTableAttribute))) return new List(0); + var tableByEntity = _commonUtils.GetTableByEntity(s); + var keyValuePairs = tableByEntity.Properties.Where(pair => + pair.Value.GetCustomAttribute() != null); + return keyValuePairs.Select(keyValuePair => keyValuePair.Value.Name).ToList(); + }); + if (InternalIsIgnoreInto == false) return base.ToSqlValuesOrSelectUnionAll(ignoreColumn: ignoreColumnList); + var sql = base.ToSqlValuesOrSelectUnionAll(ignoreColumn: ignoreColumnList); + return $"INSERT IGNORE INTO {sql.Substring(12)}"; + } + + private static ConcurrentDictionary> _ignoreInsertColumns = + new ConcurrentDictionary>(); + + protected override long RawExecuteIdentity() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + sql = string.Concat(sql, "; SELECT LAST_INSERT_ID();"); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + long ret = 0; + Exception exception = null; + try + { + ret = long.TryParse( + string.Concat(_orm.Ado.ExecuteScalar(_connection, _transaction, CommandType.Text, sql, + _commandTimeout, _params)), out var trylng) + ? trylng + : 0; + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + + return ret; + } + + protected override List RawExecuteInserted() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(sql).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ") + .Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + + sql = sb.ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + var ret = new List(); + Exception exception = null; + try + { + ret = _orm.Ado.Query(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, + sql, _commandTimeout, _params); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + + return ret; + } + +#if net40 +#else + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => + base.SplitExecuteAffrowsAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, + _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + + public override Task ExecuteIdentityAsync(CancellationToken cancellationToken = default) => + base.SplitExecuteIdentityAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, + _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + + public override Task> ExecuteInsertedAsync(CancellationToken cancellationToken = default) => + base.SplitExecuteInsertedAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, + _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + + protected override async Task RawExecuteIdentityAsync(CancellationToken cancellationToken = default) + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + sql = string.Concat(sql, "; SELECT LAST_INSERT_ID();"); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + long ret = 0; + Exception exception = null; + try + { + ret = long.TryParse( + string.Concat(await _orm.Ado.ExecuteScalarAsync(_connection, _transaction, CommandType.Text, sql, + _commandTimeout, _params, cancellationToken)), out var trylng) + ? trylng + : 0; + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + + return ret; + } + + protected override async Task> RawExecuteInsertedAsync(CancellationToken cancellationToken = default) + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(sql).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ") + .Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + + sql = sb.ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + var ret = new List(); + Exception exception = null; + try + { + ret = await _orm.Ado.QueryAsync(_table.TypeLazy ?? _table.Type, _connection, _transaction, + CommandType.Text, sql, _commandTimeout, _params, cancellationToken); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + + return ret; + } +#endif + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/Curd/TDengineSelect.cs b/Providers/FreeSql.Provider.TDengine/Curd/TDengineSelect.cs new file mode 100644 index 000000000..056fddf67 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Curd/TDengineSelect.cs @@ -0,0 +1,628 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using FreeSql.Provider.TDengine.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace FreeSql.TDengine.Curd +{ + internal class TDengineSelect : FreeSql.Internal.CommonProvider.Select1Provider + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + + } + + internal static string ToSqlStatic(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, + bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, + string _orderby, int _skip, int _limit, List _tables, + List> tbUnions, Func _aliasRule, string _tosqlAppendContent, + List _whereGlobalFilter, IFreeSql _orm) + { + if (_orm.CodeFirst.IsAutoSyncStructure) + _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray()); + + if (_whereGlobalFilter.Any()) + foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent)) + { + tb.Cascade = + _commonExpression.GetWhereCascadeSql(tb, _whereGlobalFilter.Where(a => a.Before == false), + true); + tb.CascadeBefore = + _commonExpression.GetWhereCascadeSql(tb, _whereGlobalFilter.Where(a => a.Before == true), true); + } + + var sb = new StringBuilder(); + var tbUnionsGt0 = tbUnions.Count > 1; + for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++) + { + if (tbUnionsIdx > 0) sb.Append("\r\n \r\nUNION ALL\r\n \r\n"); + if (tbUnionsGt0) sb.Append(_select).Append(" * from ("); + var tbUnion = tbUnions[tbUnionsIdx]; + var sbnav = new StringBuilder(); + sb.Append(_select); + if (_distinct) sb.Append("DISTINCT "); + sb.Append(field).Append(" \r\nFROM "); + var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray(); + var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray(); + for (var a = 0; a < tbsfrom.Length; a++) + { + var aTableInfo = tbsfrom[a].Table; + var aTbFrom = tbUnion[aTableInfo.Type]; + //适配 TDengine 超表 + //aTbFrom = TDengineTableNameAdapter(ref aTableInfo, ref aTbFrom, ref _commonUtils); + sb.Append(_commonUtils.QuoteSqlName(aTbFrom)).Append(" ") + .Append(_aliasRule?.Invoke(aTableInfo.Type, tbsfrom[a].Alias) ?? tbsfrom[a].Alias); + if (tbsjoin.Length > 0) + { + //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1 + for (var b = 1; b < tbsfrom.Length; b++) + { + var bTbFrom = tbUnion[tbsfrom[b].Table.Type]; + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(bTbFrom)).Append(" ") + .Append(_aliasRule?.Invoke(tbsfrom[b].Table.Type, tbsfrom[b].Alias) ?? + tbsfrom[b].Alias); + + if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && + string.IsNullOrEmpty(tbsfrom[b].On) && + string.IsNullOrEmpty(tbsfrom[b].Cascade) && + string.IsNullOrEmpty(tbsfrom[b].CascadeBefore)) sb.Append(" ON 1 = 1"); + else + sb.Append(" ON ").Append(string.Join(" AND ", new[] + { + tbsfrom[b].CascadeBefore, + tbsfrom[b].NavigateCondition ?? tbsfrom[b].On, + tbsfrom[b].Cascade + }.Where(sql => string.IsNullOrEmpty(sql) == false))); + } + + break; + } + else + { + if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].CascadeBefore)) + sbnav.Append(" AND ").Append(tbsfrom[a].CascadeBefore); + if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) + sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")"); + if (!string.IsNullOrEmpty(tbsfrom[a].On)) + sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")"); + if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) + sbnav.Append(" AND ").Append(tbsfrom[a].Cascade); + } + + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + + foreach (var tb in tbsjoin) + { + switch (tb.Type) + { + case SelectTableInfoType.Parent: + case SelectTableInfoType.RawJoin: + continue; + case SelectTableInfoType.LeftJoin: + sb.Append(" \r\nLEFT JOIN "); + break; + case SelectTableInfoType.InnerJoin: + sb.Append(" \r\nINNER JOIN "); + break; + case SelectTableInfoType.RightJoin: + sb.Append(" \r\nRIGHT JOIN "); + break; + } + + sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ") + .Append(_aliasRule?.Invoke(tb.Table.Type, tb.Alias) ?? tb.Alias) + .Append(" ON ").Append(string.Join(" AND ", new[] + { + tb.CascadeBefore, + tb.On ?? tb.NavigateCondition, + tb.Cascade + }.Where(sql => string.IsNullOrEmpty(sql) == false))); + if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) + sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")"); + } + + if (_join.Length > 0) sb.Append(_join); + + if (!string.IsNullOrEmpty(_tables[0].CascadeBefore)) + sbnav.Append(" AND ").Append(_tables[0].CascadeBefore); + sbnav.Append(_where); + if (!string.IsNullOrEmpty(_tables[0].Cascade)) sbnav.Append(" AND ").Append(_tables[0].Cascade); + + if (sbnav.Length > 0) + { + sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5)); + } + + if (string.IsNullOrEmpty(_groupby) == false) + { + sb.Append(_groupby); + if (string.IsNullOrEmpty(_having) == false) + sb.Append(" \r\nHAVING ").Append(_having.Substring(5)); + } + + sb.Append(_orderby); + if (_skip > 0 || _limit > 0) + sb.Append(" \r\nlimit ").Append(Math.Max(0, _skip)).Append(",").Append(_limit > 0 ? _limit : -1); + + sbnav.Clear(); + if (tbUnionsGt0) sb.Append(") ftb"); + } + + return sb.Append(_tosqlAppendContent).ToString(); + } + + public override ISelect From( + Expression, T2, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, + null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> + exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, _commonExpression, + null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, + _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, + _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, _commonUtils, + _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, + _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, + _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 + , ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new TDengineSelect(_orm, + _commonUtils, _commonExpression, null); + TDengineSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _commonExpression, _select, + _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, + _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, + _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select2Provider where T2 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select3Provider + where T2 : class where T3 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select4Provider + where T2 : class where T3 : class where T4 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select5Provider + where T2 : class where T3 : class where T4 : class where T5 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select6Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select7Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select8Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select9Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider.Select10Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider. + Select11Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider. + Select12Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + where T12 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider. + Select13Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + where T12 : class + where T13 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal.CommonProvider. + Select14Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + where T12 : class + where T13 : class + where T14 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal. + CommonProvider.Select15Provider + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + where T12 : class + where T13 : class + where T14 : class + where T15 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class TDengineSelect : FreeSql.Internal. + CommonProvider.Select16Provider + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class + where T11 : class + where T12 : class + where T13 : class + where T14 : class + where T15 : class + where T16 : class + { + public TDengineSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => TDengineSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/Describes/SuperTableDescribe.cs b/Providers/FreeSql.Provider.TDengine/Describes/SuperTableDescribe.cs new file mode 100644 index 000000000..65754563c --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/Describes/SuperTableDescribe.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.TDengine.Describes +{ + internal class SuperTableDescribe + { + /// + /// 超级表Type + /// + public Type SuperTableType { get; set; } + + /// + /// 超级表名称 + /// + public string SuperTableName { get; set; } + + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/FreeSql - Backup.Provider.TDengine.csproj b/Providers/FreeSql.Provider.TDengine/FreeSql - Backup.Provider.TDengine.csproj new file mode 100644 index 000000000..1dd35d8d3 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/FreeSql - Backup.Provider.TDengine.csproj @@ -0,0 +1,40 @@ + + + + net8.0;net7.0;net6.0;netstandard2.0;net451;net45; + true + FreeSql;ncc;YeXiangQin;Daily + FreeSql 数据库实现,基于TDengine.Connector + https://github.com/2881099/FreeSql + https://github.com/2881099/FreeSql + git + MIT + FreeSql;ORM;SqlServer;mssql + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + true + false + key.snk + 3.5.100-preview20240821 + readme.md + + + + + + + + + + + + + + + + + + diff --git a/Providers/FreeSql.Provider.TDengine/FreeSql.Provider.TDengine.csproj b/Providers/FreeSql.Provider.TDengine/FreeSql.Provider.TDengine.csproj new file mode 100644 index 000000000..02bb5f511 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/FreeSql.Provider.TDengine.csproj @@ -0,0 +1,39 @@ + + + + net8.0;net7.0;net6.0;netstandard2.0;net451;net45; + true + FreeSql;ncc;YeXiangQin;Daily + FreeSql 数据库实现,基于TDengine.Connector + https://github.com/2881099/FreeSql + https://github.com/2881099/FreeSql + git + MIT + FreeSql;ORM;SqlServer;mssql + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + true + false + key.snk + 3.5.100-preview20240821 + readme.md + + + + + + + + + + + + + + + + + diff --git a/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs new file mode 100644 index 000000000..1dcf404b9 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineAdo.cs @@ -0,0 +1,111 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.Internal.Model; +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections; +using System.Data.Common; +using System.Linq; +using System.Threading; +using TDengine.Data.Client; + +namespace FreeSql.TDengine +{ + internal class TDengineAdo : AdoProvider + { + public TDengineAdo() : base(DataType.TDengine, null, null) { } + + public TDengineAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, + Func connectionFactory) : base(DataType.TDengine, masterConnectionString, + slaveConnectionStrings) + { + base._util = util; + if (connectionFactory != null) + { + var pool = new DbConnectionPool(DataType.TDengine, connectionFactory); + ConnectionString = pool.TestConnection?.ConnectionString; + MasterPool = pool; + return; + } + + var isAdoPool = masterConnectionString?.StartsWith("AdoConnectionPool,") ?? false; + if (isAdoPool) masterConnectionString = masterConnectionString.Substring("AdoConnectionPool,".Length); + if (!string.IsNullOrEmpty(masterConnectionString)) + MasterPool = isAdoPool + ? new DbConnectionStringPool(base.DataType, CoreErrorStrings.S_MasterDatabase, + () => new TDengineConnection(masterConnectionString)) as IObjectPool + : new TDengineConnectionPool(CoreErrorStrings.S_MasterDatabase, masterConnectionString, null, null); + + slaveConnectionStrings?.ToList().ForEach(slaveConnectionString => + { + var slavePool = isAdoPool + ? new DbConnectionStringPool(base.DataType, $"{CoreErrorStrings.S_SlaveDatabase}{SlavePools.Count + 1}", + () => new TDengineConnection(slaveConnectionString)) as IObjectPool + : new TDengineConnectionPool($"{CoreErrorStrings.S_SlaveDatabase}{SlavePools.Count + 1}", + slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), + () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + }); + } + + public override object AddslashesProcessParam(object param, Type mapType, ColumnInfo mapColumn) + { + if (param == null) return "NULL"; + + if (mapType != null && mapType != param.GetType() && (param is IEnumerable == false)) + param = Utils.GetDataReaderValue(mapType, param); + + if (param is bool paramBool) + return paramBool ? "true" : "false"; + else if (param is string paramStr) + return string.Concat("'", paramStr?.Replace("'", "\\'"), "'"); + else if (param is char) + return string.Concat("'", param.ToString()?.Replace("'", "''").Replace('\0', ' '), "'"); + else if (param is Enum @enum) + return AddslashesTypeHandler(@enum.GetType(), @enum) ?? @enum.ToInt64(); + else if (decimal.TryParse(string.Concat(param), out _)) + return param; + + else if (param is DateTime time) + return AddslashesTypeHandler(typeof(DateTime), time) ?? + string.Concat("'", time.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), "'"); + +#if NET6_0_OR_GREATER + + else if (param is DateOnly dateOnly) + return AddslashesTypeHandler(typeof(DateOnly), dateOnly) ?? + string.Concat("'", dateOnly.ToString("yyyy-MM-dd"), "'"); + else if (param is TimeOnly timeOnly) + { + return $"'{timeOnly.Hour}:{timeOnly.Minute}:{timeOnly.Second}'"; + } +#endif + + else if (param is TimeSpan timeSpan) + { + return $"'{Math.Floor(timeSpan.TotalHours)}:{timeSpan.Minutes}:{timeSpan.Seconds}'"; + } + else if (param is byte[] bytes) + return $"0x{CommonUtils.BytesSqlRaw(bytes)}"; + + else if (param is IEnumerable) + return AddslashesIEnumerable(param, mapType, mapColumn); + + return string.Concat("'", param.ToString()?.Replace("\\", "\\\\").Replace("'", "\\'"), "'"); + } + + public override DbCommand CreateCommand() + { + return new TDengineCommand(); + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + _util.GetDbParamtersByObject(sql, obj); + + public override void ReturnConnection(IObjectPool pool, Object conn, Exception ex) + { + if (pool is TDengineConnectionPool rawPool) rawPool.Return(conn, ex); + else pool.Return(conn); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs new file mode 100644 index 000000000..15042e265 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineAdo/TDengineConnectionPool.cs @@ -0,0 +1,255 @@ +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections.Concurrent; +using System.Data; +using System.Data.Common; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using TDengine.Data.Client; +using TDengine.Driver; +using TDengine.Driver.Client; + +namespace FreeSql.TDengine +{ + 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; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) + { + base.Return(obj, isRecreate); + } + } + + internal class TDengineConnectionPoolPolicy : IPolicy + { + internal TDengineConnectionPool InternalPool; + public string Name { get; set; } = $"TDengine Connection {CoreErrorStrings.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(CoreErrorStrings.S_ConnectionStringError), + obj.LastGetTimeCopy); + throw new Exception(CoreErrorStrings.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(CoreErrorStrings.S_ConnectionStringError), + obj.LastGetTimeCopy); + throw new Exception(CoreErrorStrings.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..981d0715b --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineCodeFirst.cs @@ -0,0 +1,308 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using FreeSql.DataAnnotations; +using System.Reflection; +using FreeSql.Internal.ObjectPool; +using FreeSql.Provider.TDengine.Attributes; + +namespace FreeSql.TDengine +{ + internal class TDengineCodeFirst : Internal.CommonProvider.CodeFirstProvider + { + public TDengineCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, + commonUtils, commonExpression) + { + } + + static readonly Dictionary> DicCsToDb = new Dictionary>() + { + { typeof(bool).FullName, CsToDb.New(DbType.Boolean, "BOOL", "BOOL", null, false, null) }, + { typeof(bool?).FullName, CsToDb.New(DbType.Boolean, "BOOL", "BOOL", null, true, null) }, + { typeof(DateTime).FullName, CsToDb.New(DbType.DateTime, "TIMESTAMP", "TIMESTAMP", null, false, null) }, + { typeof(DateTime?).FullName, CsToDb.New(DbType.DateTime, "TIMESTAMP", "TIMESTAMP", null, true, null) }, + { typeof(TimeSpan).FullName, CsToDb.New(DbType.DateTime, "TIMESTAMP", "TIMESTAMP", null, false, null) }, + { typeof(TimeSpan?).FullName, CsToDb.New(DbType.DateTime, "TIMESTAMP", "TIMESTAMP", null, true, null) }, + { typeof(short).FullName, CsToDb.New(DbType.Int16, "SMALLINT", "SMALLINT", null, false, 0) }, + { typeof(short?).FullName, CsToDb.New(DbType.Int16, "SMALLINT", "SMALLINT", null, true, null) }, + { typeof(int).FullName, CsToDb.New(DbType.Int32, "INT", "INT", null, false, 0) }, + { typeof(int?).FullName, CsToDb.New(DbType.Int32, "INT", "INT", null, true, null) }, + { typeof(sbyte).FullName, CsToDb.New(DbType.SByte, "TINYINT", "TINYINT", null, false, 0) }, + { typeof(sbyte?).FullName, CsToDb.New(DbType.SByte, "TINYINT", "TINYINT", null, true, null) }, + { typeof(long).FullName, CsToDb.New(DbType.Int64, "BIGINT", "BIGINT", null, false, 0) }, + { typeof(long?).FullName, CsToDb.New(DbType.Int64, "BIGINT", "BIGINT", null, true, null) }, + { typeof(byte).FullName, CsToDb.New(DbType.Byte, "TINYINT UNSIGNED", "TINYINT UNSIGNED", null, false, 0) }, + { + typeof(byte?).FullName, + CsToDb.New(DbType.Byte, "TINYINT UNSIGNED", "TINYINT UNSIGNED", null, true, null) + }, + { + typeof(ushort).FullName, + CsToDb.New(DbType.UInt16, "SMALLINT UNSIGNED", "SMALLINT UNSIGNED", null, false, 0) + }, + { + typeof(ushort?).FullName, + CsToDb.New(DbType.UInt16, "SMALLINT UNSIGNED", "SMALLINT UNSIGNED", null, true, null) + }, + { typeof(uint).FullName, CsToDb.New(DbType.UInt32, "INT UNSIGNED", "INT UNSIGNED", null, false, 0) }, + { typeof(uint?).FullName, CsToDb.New(DbType.UInt32, "INT UNSIGNED", "INT UNSIGNED", null, true, null) }, + { typeof(ulong).FullName, CsToDb.New(DbType.UInt64, "BIGINT UNSIGNED", "BIGINT UNSIGNED", null, false, 0) }, + { + typeof(ulong?).FullName, + CsToDb.New(DbType.UInt64, "BIGINT UNSIGNED", "BIGINT UNSIGNED", null, true, null) + }, + { typeof(float).FullName, CsToDb.New(DbType.Single, "FLOAT", "FLOAT", null, false, 0) }, + { typeof(float?).FullName, CsToDb.New(DbType.Single, "FLOAT", "FLOAT", null, true, null) }, + { typeof(double).FullName, CsToDb.New(DbType.Double, "DOUBLE", "DOUBLE", null, false, 0) }, + { typeof(double?).FullName, CsToDb.New(DbType.Double, "DOUBLE", "DOUBLE", null, true, null) }, + { typeof(string).FullName, CsToDb.New(DbType.String, "NCHAR", "NCHAR(255)", null, false, 0) }, + }; + + public override DbInfoResult GetDbInfo(Type type) + { + if (DicCsToDb.TryGetValue(type.FullName, out var trydc)) + return new DbInfoResult((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, + trydc.defaultValue); + if (type.IsArray) return null; + return null; + } + + protected override string GetComparisonDDLStatements(params TypeSchemaAndName[] objects) + { + Object conn = null; + string database = null; + var sb = new StringBuilder(); + try + { + conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5)); + database = conn.Value.Database; + foreach (var obj in objects) + { + if (sb.Length > 0) sb.Append(Environment.NewLine); + var tb = obj.tableSchema; + if (tb == null) + throw new Exception(CoreErrorStrings.S_Type_IsNot_Migrable(obj.tableSchema.Type.FullName)); + if (tb.Columns.Any() == false) + throw new Exception( + CoreErrorStrings.S_Type_IsNot_Migrable_0Attributes(obj.tableSchema.Type.FullName)); + + var tbName = _commonUtils.SplitTableName(tb.DbName).First(); + + tbName = _commonUtils.QuoteSqlName(database, tbName); + + if (!TryTableExists(tbName)) + { + TableHandle(ref tb, ref database, tb.Type, ref sb, tbName); + } + } + } + finally + { + try + { + if (string.IsNullOrEmpty(database) == false) + conn.Value.ChangeDatabase(database); + _orm.Ado.MasterPool.Return(conn); + } + catch + { + _orm.Ado.MasterPool.Return(conn, true); + } + } + + + var ddl = sb.Length == 0 ? null : sb.ToString(); + return ddl; + } + + private void CreateColumns(ref TableInfo tb, ref StringBuilder sb) + { + //创建表 + foreach (var columnInfo in tb.ColumnsByPosition.Where(c => + !c.Table.Properties[c.CsName].IsDefined(typeof(TDengineTagAttribute)))) + { + sb.Append($" {Environment.NewLine} ").Append(_commonUtils.QuoteSqlName(columnInfo.Attribute.Name)) + .Append(" ") + .Append(columnInfo.Attribute.DbType); + sb.Append(","); + } + + sb.Remove(sb.Length - 1, 1).Append($"{Environment.NewLine})"); + } + + private void TableHandle(ref TableInfo tb, ref string database, Type type, ref StringBuilder sb, string tbName) + { + //判断是否超级表 + var subTableAttribute = type.GetCustomAttribute(); + + //要创建表的为子表 + if (subTableAttribute != null) + { + if (_commonUtils is TDengineUtils utils) + { + var superTableDescribe = utils.GetSuperTableDescribe(type); + + if (superTableDescribe == null) return; + + var superTableName = _commonUtils.QuoteSqlName(database, superTableDescribe.SuperTableName); + var superTableInfo = GetTableByEntity(superTableDescribe.SuperTableType); + + //判断超表是否存在 + if (!TryTableExists(superTableName)) + { + //先创建超级表 + CreateSuperTable(ref superTableInfo, ref sb, superTableName); + _orm.Ado.ExecuteNonQuery(sb.ToString()); + sb = sb.Clear(); + } + + var subTableName = _commonUtils.QuoteSqlName(database, subTableAttribute.Name); + + //创建子表 + CreateSubTable(ref tb, ref sb, superTableName, subTableName, ref superTableInfo); + } + } + //要创建的为超级表 + else if (type.IsDefined(typeof(TDengineSuperTableAttribute))) + { + var superTableAttribute = type.GetCustomAttribute(); + if (superTableAttribute == null) return; + tbName = _commonUtils.QuoteSqlName(database, superTableAttribute.Name); + CreateSuperTable(ref tb, ref sb, tbName); + } + //创建普通表 + else + { + CreateNormalTable(ref tb, ref sb, tbName); + } + } + + + /// + /// 创建子表 + /// + /// + /// + /// + private void CreateSubTable(ref TableInfo childTableInfo, ref StringBuilder sb, string superTableName, + string subTableName, ref TableInfo + superTableInfo) + { + sb.Append($"CREATE TABLE {subTableName}{Environment.NewLine}"); + sb.Append($"USING {superTableName} ("); + + var tagCols = superTableInfo.ColumnsByPosition.Where(c => + c.Table.Properties[c.CsName].IsDefined(typeof(TDengineTagAttribute))).ToArray(); + + var tagValues = new List(tagCols.Count()); + + var tableInstance = Activator.CreateInstance(childTableInfo.Type); + + foreach (var columnInfo in tagCols) + { + var tagValue = childTableInfo.Properties[columnInfo.CsName].GetValue(tableInstance); + tagValues.Add(tagValue); + sb.Append($" {Environment.NewLine} ").Append(_commonUtils.QuoteSqlName(columnInfo.Attribute.Name)) + .Append(","); + } + + sb.Remove(sb.Length - 1, 1).Append($"{Environment.NewLine}) TAGS ("); + + foreach (var tagValue in tagValues) + { + sb.Append($" {Environment.NewLine} ").Append(HandleTagValue(tagValue)).Append(","); + } + + sb.Remove(sb.Length - 1, 1).Append($"{Environment.NewLine});"); + } + + /// + /// 创建超级表 + /// + /// + /// + /// + private void CreateSuperTable(ref TableInfo tb, ref StringBuilder sb, string superTableName) + { + sb.Append($"CREATE STABLE {superTableName} ("); + CreateColumns(ref tb, ref sb); + sb.Append($" TAGS ("); + + var columInfos = tb.ColumnsByPosition.Where(c => + c.Table.Properties[c.CsName].IsDefined(typeof(TDengineTagAttribute))); + + foreach (var columnInfo in columInfos) + { + sb.Append($" {Environment.NewLine} ").Append(_commonUtils.QuoteSqlName(columnInfo.Attribute.Name)) + .Append(" ") + .Append(columnInfo.Attribute.DbType); + sb.Append(","); + } + + sb.Remove(sb.Length - 1, 1).Append($"{Environment.NewLine});"); + } + + /// + /// 创建普通表 + /// + /// + /// + /// + private void CreateNormalTable(ref TableInfo tb, ref StringBuilder sb, string normalTableName) + { + sb.Append($"CREATE TABLE {normalTableName} ("); + CreateColumns(ref tb, ref sb); + foreach (var columnInfo in tb.ColumnsByPosition.Where(c => + c.Table.Properties[c.CsName].IsDefined(typeof(TDengineTagAttribute)))) + { + sb.Append($" {Environment.NewLine} ").Append(_commonUtils.QuoteSqlName(columnInfo.Attribute.Name)) + .Append(" ") + .Append(columnInfo.Attribute.DbType); + sb.Append(","); + } + + sb.Remove(sb.Length - 1, 1).Append(");"); + } + + private bool TryTableExists(string tbName) + { + var flag = true; + try + { + var executeScalar = _orm.Ado.ExecuteScalar(CommandType.Text, + $"DESCRIBE {tbName}"); + + if (executeScalar == null) + { + flag = false; + } + } + catch (Exception e) + { + if (e.Message.Contains("Table does not exist")) + { + flag = false; + } + } + + return flag; + } + + private object HandleTagValue(object tagValue) + { + if (tagValue is DateTime || tagValue is string) + { + return $"\"{tagValue}\""; + } + else + { + return tagValue; + } + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs b/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs new file mode 100644 index 000000000..d9d1f9c35 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineDbFirst.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; + +namespace FreeSql.TDengine +{ + public class TDengineDbFirst : IDbFirst + { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + + public TDengineDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public List GetDatabases() + { + var sql = @"SHOW DATABASES;"; + var ds = _orm.Ado.Query(sql); + return ds; + } + + public List GetTablesByDatabase(params string[] database) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + } + + public DbTableInfo GetTableByName(string name, bool ignoreCase = true) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + } + + public bool ExistsTable(string name, bool ignoreCase = true) + { + var sb = new StringBuilder(); + sb.Append("select count(1) from information_schema.ins_tables where "); + var where = ignoreCase ? $"LOWER(table_name) = LOWER('{name}')" : $"table_name = '{name}'"; + sb.Append(where); + var sql = sb.ToString(); + var executeScalar = _orm.Ado.ExecuteScalar(sql); + var result = Convert.ToInt32(executeScalar); + return result > 0; + } + + public int GetDbType(DbColumnInfo column) => (int)GetTDengineDbType(column); + + DbType GetTDengineDbType(DbColumnInfo column) + { + switch (column.DbTypeText) + { + case "DOUBLE": return DbType.Double; + case "FLOAT": return DbType.Single; + case "TIMESTAMP": return DbType.DateTime; + case "BOOL": return DbType.Boolean; + case "NCHAR": return DbType.String; + case "TINYINT UNSIGNED": return DbType.Byte; + case "SMALLINT UNSIGNED": return DbType.UInt16; + case "INT UNSIGNED": return DbType.UInt32; + case "BIGINT UNSIGNED": return DbType.UInt64; + case "SMALLINT": return DbType.Int16; + case "INT": return DbType.Int32; + case "BIGINT": return DbType.Int64; + default: return DbType.String; + } + } + + static readonly Dictionary _dicDbToCs = new Dictionary() + { + }; + + public string GetCsConvert(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) + // ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) + // : null; + } + + public string GetCsParse(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + } + + public string GetCsStringify(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + } + + + public string GetCsType(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) + // ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) + // : null; + } + + public Type GetCsTypeInfo(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + } + + public string GetCsTypeValue(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + } + + public string GetDataReaderMethod(DbColumnInfo column) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + //return _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + } + + public List GetEnumsByDatabase(params string[] database) + { + + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineExpression.cs b/Providers/FreeSql.Provider.TDengine/TDengineExpression.cs new file mode 100644 index 000000000..4207dd431 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineExpression.cs @@ -0,0 +1,584 @@ +using FreeSql.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text.RegularExpressions; +using System.Text; + +namespace FreeSql.TDengine +{ + internal class TDengineExpression : CommonExpression + { + public TDengineExpression(CommonUtils common) : base(common) + { + } + public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + switch (exp.NodeType) + { + case ExpressionType.ArrayLength: + var arrOper = (exp as UnaryExpression)?.Operand; + var arrOperExp = getExp(arrOper); + if (arrOperExp.StartsWith("(") || arrOperExp.EndsWith(")")) return $"len([{arrOperExp.TrimStart('(').TrimEnd(')')}])"; + if (arrOper.Type == typeof(byte[])) return $"octet_length({getExp(arrOper)})"; + return $"case when {arrOperExp} is null then 0 else len({arrOperExp}) end"; + case ExpressionType.Convert: + var operandExp = (exp as UnaryExpression)?.Operand; + var gentype = exp.Type.NullableTypeOrThis(); + if (gentype != operandExp.Type.NullableTypeOrThis()) + { + switch (exp.Type.NullableTypeOrThis().ToString()) + { + case "System.Boolean": return $"({getExp(operandExp)} not in ('0','false'))"; + case "System.Byte": return $"cast({getExp(operandExp)} as utinyint)"; + case "System.Char": return $"substr(cast({getExp(operandExp)} as char), 1, 1)"; + case "System.DateTime": return ExpressionConstDateTime(operandExp) ?? $"cast({getExp(operandExp)} as timestamp)"; + case "System.Decimal": return $"cast({getExp(operandExp)} as decimal(36,18))"; + case "System.Double": return $"cast({getExp(operandExp)} as double)"; + case "System.Int16": return $"cast({getExp(operandExp)} as smallint)"; + case "System.Int32": return $"cast({getExp(operandExp)} as integer)"; + case "System.Int64": return $"cast({getExp(operandExp)} as bigint)"; + case "System.SByte": return $"cast({getExp(operandExp)} as tinyint)"; + case "System.Single": return $"cast({getExp(operandExp)} as float)"; + case "System.String": return $"cast({getExp(operandExp)} as text)"; + case "System.UInt16": return $"cast({getExp(operandExp)} as usmallint)"; + case "System.UInt32": return $"cast({getExp(operandExp)} as uinteger)"; + case "System.UInt64": return $"cast({getExp(operandExp)} as ubigint)"; + case "System.Guid": return $"cast({getExp(operandExp)} as uuid)"; + } + } + break; + case ExpressionType.Call: + var callExp = exp as MethodCallExpression; + + switch (callExp.Method.Name) + { + case "Parse": + case "TryParse": + switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString()) + { + case "System.Boolean": return $"({getExp(callExp.Arguments[0])} not in ('0','false'))"; + case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} as utinyint)"; + case "System.Char": return $"substr(cast({getExp(callExp.Arguments[0])} as char), 1, 1)"; + case "System.DateTime": return ExpressionConstDateTime(callExp.Arguments[0]) ?? $"cast({getExp(callExp.Arguments[0])} as timestamp)"; + case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as decimal(36,18))"; + case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as double)"; + case "System.Int16": return $"cast({getExp(callExp.Arguments[0])} as smallint)"; + case "System.Int32": return $"cast({getExp(callExp.Arguments[0])} as integer)"; + case "System.Int64": return $"cast({getExp(callExp.Arguments[0])} as bigint)"; + case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as tinyint)"; + case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as float)"; + case "System.UInt16": return $"cast({getExp(callExp.Arguments[0])} as usmallint)"; + case "System.UInt32": return $"cast({getExp(callExp.Arguments[0])} as uinteger)"; + case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} as ubigint)"; + case "System.Guid": return $"cast({getExp(callExp.Arguments[0])} as uuid)"; + } + return null; + case "NewGuid": + return null; + case "Next": + if (callExp.Object?.Type == typeof(Random)) return "cast(random()*1000000000 as int)"; + return null; + case "NextDouble": + if (callExp.Object?.Type == typeof(Random)) return "random()"; + return null; + case "Random": + if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; + return null; + case "ToString": + if (callExp.Object != null) + { + if (callExp.Object.Type.NullableTypeOrThis().IsEnum) + { + tsc.SetMapColumnTmp(null); + var oldMapType = tsc.SetMapTypeReturnOld(typeof(string)); + var enumStr = ExpressionLambdaToSql(callExp.Object, tsc); + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + return enumStr; + } + var value = ExpressionGetValue(callExp.Object, out var success); + if (success) return formatSql(value, typeof(string), null, null); + return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::text" : null; + } + return null; + } + + var objExp = callExp.Object; + var objType = objExp?.Type; + if (objType?.FullName == "System.Byte[]") return null; + + var argIndex = 0; + if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) + { + objExp = callExp.Arguments.FirstOrDefault(); + objType = objExp?.Type; + argIndex++; + + if (objType == typeof(string)) + { + switch (callExp.Method.Name) + { + case "First": + case "FirstOrDefault": + return $"substring({getExp(callExp.Arguments[0])}, 1, 1)"; + } + } + } + if (objType == null) objType = callExp.Method.DeclaringType; + if (objType != null || objType.IsArrayOrList()) + { + string left = null; + switch (callExp.Method.Name) + { + case "Any": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"[{left.TrimStart('(').TrimEnd(')')}]"; + return $"(case when {left} is null then 0 else len({left}) end > 0)"; + case "Contains": + tsc.SetMapColumnTmp(null); + var args1 = getExp(callExp.Arguments[argIndex]); + var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess ? tsc.SetDbParamsReturnOld(null) : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug + tsc.isNotSetMapColumnTmp = true; + left = objExp == null ? null : getExp(objExp); + tsc.isNotSetMapColumnTmp = false; + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); + //判断 in 或 array @> array + if (left.StartsWith("[") && left.EndsWith("]")) + return $"({args1}) in ({left.TrimStart('[').TrimEnd(']')})"; + if (left.StartsWith("(") && left.EndsWith(")")) //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 + return $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; + return $"list_contains({left}, {args1})"; + case "Concat": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"[{left.TrimStart('(').TrimEnd(')')}]"; + var right2 = getExp(callExp.Arguments[argIndex]); + if (right2.StartsWith("(") || right2.EndsWith(")")) right2 = $"[{right2.TrimStart('(').TrimEnd(')')}]"; + return $"list_concat({left}, {right2})"; + case "GetLength": + case "GetLongLength": + case "Length": + case "Count": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"[{left.TrimStart('(').TrimEnd(')')}]"; + return $"case when {left} is null then 0 else len({left}) end"; + } + if (objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + left = objExp == null ? null : getExp(objExp); + switch (callExp.Method.Name) + { + case "get_Item": return $"element_at({left},{getExp(callExp.Arguments[argIndex])})[1]"; + case "ContainsKey": return $"len(element_at({left},{getExp(callExp.Arguments[argIndex])})) > 0"; + case "GetLength": + case "GetLongLength": + case "Count": return $"cardinality({left})"; + case "Keys": return $"map_keys({left})"; + case "Values": return $"map_values({left})"; + } + } + } + break; + case ExpressionType.MemberAccess: + var memExp = exp as MemberExpression; + var memParentExp = memExp.Expression?.Type; + if (memParentExp?.FullName == "System.Byte[]") return null; + if (memParentExp != null) + { + if (memParentExp.IsArrayOrList()) + { + var left = getExp(memExp.Expression); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"[{left.TrimStart('(').TrimEnd(')')}]"; + switch (memExp.Member.Name) + { + case "Length": + case "Count": return $"case when {left} is null then 0 else len({left}) end"; + } + } + if (memParentExp.IsGenericType && memParentExp.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var left = getExp(memExp.Expression); + switch (memExp.Member.Name) + { + case "Count": return $"cardinality({left})"; + case "Keys": return $"map_keys({left})"; + case "Values": return $"map_values({left})"; + } + } + } + break; + case ExpressionType.NewArrayInit: + var arrExp = exp as NewArrayExpression; + var arrSb = new StringBuilder(); + arrSb.Append("["); + for (var a = 0; a < arrExp.Expressions.Count; a++) + { + if (a > 0) arrSb.Append(","); + arrSb.Append(getExp(arrExp.Expressions[a])); + } + if (arrSb.Length == 1) arrSb.Append("NULL"); + return arrSb.Append("]").ToString(); + case ExpressionType.ListInit: + var listExp = exp as ListInitExpression; + var listSb = new StringBuilder(); + listSb.Append("("); + for (var a = 0; a < listExp.Initializers.Count; a++) + { + if (listExp.Initializers[a].Arguments.Any() == false) continue; + if (a > 0) listSb.Append(","); + listSb.Append(getExp(listExp.Initializers[a].Arguments.FirstOrDefault())); + } + if (listSb.Length == 1) listSb.Append("NULL"); + return listSb.Append(")").ToString(); + case ExpressionType.New: + var newExp = exp as NewExpression; + if (typeof(IList).IsAssignableFrom(newExp.Type)) + { + if (newExp.Arguments.Count == 0) return "(NULL)"; + if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)"; + return getExp(newExp.Arguments[0]); + } + return null; + } + return null; + } + + public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Empty": return "''"; + } + return null; + } + var left = ExpressionLambdaToSql(exp.Expression, tsc); + switch (exp.Member.Name) + { + case "Length": return $"length({left})"; + } + return null; + } + public override string ExpressionLambdaToSqlMemberAccessDateTime(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Now": return _common.Now; + case "UtcNow": return _common.NowUtc; + case "Today": return "current_date"; + case "MinValue": return "timestamp '0001-01-01 00:00:00.000'"; + case "MaxValue": return "timestamp '9999-12-31 23:59:59.999'"; + } + return null; + } + var left = ExpressionLambdaToSql(exp.Expression, tsc); + switch (exp.Member.Name) + { + case "Date": return $"({left})::date"; + case "TimeOfDay": return $"strftime({left},'%H:%M:%S')::time"; + case "DayOfWeek": return $"dayofweek({left})"; + case "Day": return $"day({left})"; + case "DayOfYear": return $"dayofyear({left})"; + case "Month": return $"month({left})"; + case "Year": return $"year({left})"; + case "Hour": return $"hour({left})"; + case "Minute": return $"minute({left})"; + case "Second": return $"second({left})"; + case "Millisecond": return $"millisecond({left})"; + } + return null; + } + + public override string ExpressionLambdaToSqlCallString(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + case "IsNullOrEmpty": + var arg1 = getExp(exp.Arguments[0]); + return $"({arg1} is null or {arg1} = '')"; + case "IsNullOrWhiteSpace": + var arg2 = getExp(exp.Arguments[0]); + return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')"; + case "Concat": + if (exp.Arguments.Count == 1 && exp.Arguments[0].NodeType == ExpressionType.NewArrayInit && exp.Arguments[0] is NewArrayExpression concatNewArrExp) + return _common.StringConcat(concatNewArrExp.Expressions.Select(a => getExp(a)).ToArray(), null); + return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), null); + case "Format": + if (exp.Arguments[0].NodeType != ExpressionType.Constant) throw new Exception(CoreErrorStrings.Not_Implemented_Expression_ParameterUseConstant(exp, exp.Arguments[0])); + var expArgsHack = exp.Arguments.Count == 2 && exp.Arguments[1].NodeType == ExpressionType.NewArrayInit ? + (exp.Arguments[1] as NewArrayExpression).Expressions : exp.Arguments.Where((a, z) => z > 0); + //3个 {} 时,Arguments 解析出来是分开的 + //4个 {} 时,Arguments[1] 只能解析这个出来,然后里面是 NewArray [] + var expArgs = expArgsHack.Select(a => + { + var atype = (a as UnaryExpression)?.Operand.Type.NullableTypeOrThis() ?? a.Type.NullableTypeOrThis(); + if (atype == typeof(string)) return $"'||{_common.IsNull(ExpressionLambdaToSql(a, tsc), "''")}||'"; + return $"'||{_common.IsNull($"({ExpressionLambdaToSql(a, tsc)})::text", "''")}||'"; + }).ToArray(); + return string.Format(ExpressionLambdaToSql(exp.Arguments[0], tsc), expArgs); + case "Join": + if (exp.IsStringJoin(out var tolistObjectExp, out var toListMethod, out var toListArgs1)) + { + var newToListArgs0 = Expression.Call(tolistObjectExp, toListMethod, + Expression.Lambda( + Expression.Call( + typeof(SqlExtExtensions).GetMethod("StringJoinPgsqlGroupConcat"), + Expression.Convert(toListArgs1.Body, typeof(object)), + Expression.Convert(exp.Arguments[0], typeof(object))), + toListArgs1.Parameters)); + var newToListSql = getExp(newToListArgs0); + return newToListSql; + } + break; + } + } + else + { + var left = getExp(exp.Object); + switch (exp.Method.Name) + { + case "StartsWith": + case "EndsWith": + case "Contains": + var leftLike = exp.Object.NodeType == ExpressionType.MemberAccess ? left : $"({left})"; + var args0Value = getExp(exp.Arguments[0]); + if (args0Value == "NULL") return $"{leftLike} IS NULL"; + if (exp.Method.Name == "StartsWith") return $"{left} ^@ ({args0Value})"; + if (args0Value.Contains("%")) + { + if (exp.Method.Name == "EndsWith") return $"strpos({left}, {args0Value}) = length({left})-length({args0Value})+1"; + return $"strpos({left}, {args0Value}) > 0"; + } + if (exp.Method.Name == "EndsWith") return $"{leftLike} LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"{leftLike} LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"{leftLike} LIKE ('%' || ({args0Value})::text || '%')"; + case "ToLower": return $"lower({left})"; + case "ToUpper": return $"upper({left})"; + case "Substring": + var substrArgs1 = getExp(exp.Arguments[0]); + if (long.TryParse(substrArgs1, out var testtrylng1)) substrArgs1 = (testtrylng1 + 1).ToString(); + else substrArgs1 += "+1"; + if (exp.Arguments.Count == 1) return $"substring({left}, {substrArgs1})"; + return $"substring({left}, {substrArgs1}, {getExp(exp.Arguments[1])})"; + case "IndexOf": + var indexOfFindStr = getExp(exp.Arguments[0]); + //if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") { + // var locateArgs1 = getExp(exp.Arguments[1]); + // if (long.TryParse(locateArgs1, out var testtrylng2)) locateArgs1 = (testtrylng2 + 1).ToString(); + // else locateArgs1 += "+1"; + // return $"(instr({left}, {indexOfFindStr}, {locateArgs1})-1)"; + //} + return $"(strpos({left}, {indexOfFindStr})-1)"; + case "PadLeft": + if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])}, ' ')"; + return $"lpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "PadRight": + if (exp.Arguments.Count == 1) return $"rpad({left}, {getExp(exp.Arguments[0])}, ' ')"; + return $"rpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Trim": + case "TrimStart": + case "TrimEnd": + if (exp.Arguments.Count == 0) + { + if (exp.Method.Name == "Trim") return $"trim({left})"; + if (exp.Method.Name == "TrimStart") return $"ltrim({left})"; + if (exp.Method.Name == "TrimEnd") return $"rtrim({left})"; + } + var trimArg1 = ""; + var trimArg2 = ""; + foreach (var argsTrim02 in exp.Arguments) + { + var argsTrim01s = new[] { argsTrim02 }; + if (argsTrim02.NodeType == ExpressionType.NewArrayInit) + { + var arritem = argsTrim02 as NewArrayExpression; + argsTrim01s = arritem.Expressions.ToArray(); + } + foreach (var argsTrim01 in argsTrim01s) + { + var trimChr = getExp(argsTrim01).Trim('\''); + if (trimChr.Length == 1) trimArg1 += trimChr; + else trimArg2 += $" || ({trimChr})"; + } + } + if (exp.Method.Name == "Trim") left = $"trim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; + if (exp.Method.Name == "TrimStart") left = $"ltrim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; + if (exp.Method.Name == "TrimEnd") left = $"rtrim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; + return left; + case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; + case "Equals": return $"({left} = {getExp(exp.Arguments[0])})"; + } + } + return null; + } + public override string ExpressionLambdaToSqlCallMath(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + switch (exp.Method.Name) + { + case "Abs": return $"abs({getExp(exp.Arguments[0])})"; + case "Sign": return $"sign({getExp(exp.Arguments[0])})"; + case "Floor": return $"floor({getExp(exp.Arguments[0])})"; + case "Ceiling": return $"ceiling({getExp(exp.Arguments[0])})"; + case "Round": + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + return $"round({getExp(exp.Arguments[0])}, 2)"; + case "Exp": return $"exp({getExp(exp.Arguments[0])})"; + case "Log": return $"log({getExp(exp.Arguments[0])})"; + case "Log10": return $"log10({getExp(exp.Arguments[0])})"; + case "Pow": return $"power({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Sqrt": return $"sqrt({getExp(exp.Arguments[0])})"; + case "Cos": return $"cos({getExp(exp.Arguments[0])})"; + case "Sin": return $"sin({getExp(exp.Arguments[0])})"; + case "Tan": return $"tan({getExp(exp.Arguments[0])})"; + case "Acos": return $"acos({getExp(exp.Arguments[0])})"; + case "Asin": return $"asin({getExp(exp.Arguments[0])})"; + case "Atan": return $"atan({getExp(exp.Arguments[0])})"; + case "Atan2": return $"atan2({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Truncate": return $"trunc({getExp(exp.Arguments[0])})"; + } + return null; + } + public override string ExpressionLambdaToSqlCallDateTime(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + case "Compare": return $"(epoch({getExp(exp.Arguments[0])})-epoch({getExp(exp.Arguments[1])}))"; + case "DaysInMonth": return $"day(last_day(cast({getExp(exp.Arguments[0])}||'-'||{getExp(exp.Arguments[1])}||'-01' as date)))"; + case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})"; + + case "IsLeapYear": + var isLeapYearArgs1 = getExp(exp.Arguments[0]); + return $"(({isLeapYearArgs1})%4=0 AND ({isLeapYearArgs1})%100<>0 OR ({isLeapYearArgs1})%400=0)"; + + case "Parse": return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; + case "ParseExact": + case "TryParse": + case "TryParseExact": return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; + } + } + else + { + var left = getExp(exp.Object); + var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); + switch (exp.Method.Name) + { + case "AddDays": return $"date_add({left},cast(({args1})||' days' as interval))"; + case "AddHours": return $"date_add({left},cast(({args1})||' hours' as interval))"; + case "AddMilliseconds": return $"date_add({left},cast(({args1})||' milliseconds' as interval))"; + case "AddMinutes": return $"date_add({left},cast(({args1})||' minutes' as interval))"; + case "AddMonths": return $"date_add({left},cast(({args1})||' months' as interval))"; + case "AddSeconds": return $"date_add({left},cast(({args1})||' seconds' as interval))"; + case "AddTicks": return $"date_add({left},cast((({args1})/10)||' microseconds' as interval))"; + case "AddYears": return $"date_add({left},cast(({args1})||' years' as interval))"; + case "Subtract": + switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GetGenericArguments().FirstOrDefault() : exp.Arguments[0].Type).FullName) + { + case "System.DateTime": return $"cast((epoch({left})-epoch({args1}))||' seconds' as interval)"; + case "System.TimeSpan": return $"date_add({left},{args1})"; + } + break; + case "Equals": return $"({left} = {args1})"; + case "CompareTo": return $"(epoch({left})-epoch({args1}))"; + case "ToString": + if (exp.Arguments.Count == 0) return $"strftime({left},'%Y-%m-%d %H:%M:%S')"; + switch (args1) + { + case "'yyyy-MM-dd HH:mm:ss'": return $"strftime({left},'%Y-%m-%d %H:%M:%S')"; + case "'yyyy-MM-dd HH:mm'": return $"strftime({left},'%Y-%m-%d %H:%M')"; + case "'yyyy-MM-dd HH'": return $"strftime({left},'%Y-%m-%d %H')"; + case "'yyyy-MM-dd'": return $"strftime({left},'%Y-%m-%d')"; + case "'yyyy-MM'": return $"strftime({left},'%Y-%m')"; + case "'yyyyMMddHHmmss'": return $"strftime({left},'%Y%m%d%H%M%S')"; + case "'yyyyMMddHHmm'": return $"strftime({left},'%Y%m%d%H%M')"; + case "'yyyyMMddHH'": return $"strftime({left},'%Y%m%d%H')"; + case "'yyyyMMdd'": return $"strftime({left},'%Y%m%d')"; + case "'yyyyMM'": return $"strftime({left},'%Y%m')"; + case "'yyyy'": return $"strftime({left},'%Y')"; + case "'HH:mm:ss'": return $"strftime({left},'%H:%M:%S')"; + } + args1 = Regex.Replace(args1, "(yyyy|MM|dd|HH|mm|ss)", m => + { + switch (m.Groups[1].Value) + { + case "yyyy": return $"%Y"; + case "MM": return $"%_a1"; + case "dd": return $"%_a2"; + case "HH": return $"%_a3"; + case "mm": return $"%_a4"; + case "ss": return $"%S"; + } + return m.Groups[0].Value; + }); + var argsFinds = new[] { "%Y", "%_a1", "%_a2", "%_a3", "%_a4", "%S" }; + var argsSpts = Regex.Split(args1, "(yy|M|d|H|hh|h|m|s|tt|t)"); + for (var a = 0; a < argsSpts.Length; a++) + { + switch (argsSpts[a]) + { + case "yy": argsSpts[a] = $"substr(strftime({left},'%Y'),3,2)"; break; + case "M": argsSpts[a] = $"strftime({left},'%-m')"; break; + case "d": argsSpts[a] = $"strftime({left},'%-d')"; break; + case "H": argsSpts[a] = $"strftime({left},'%-H')"; break; + case "hh": argsSpts[a] = $"strftime({left},'%I')"; break; + case "h": argsSpts[a] = $"strftime({left},'%-I')"; break; + case "m": argsSpts[a] = $"strftime({left},'%-M')"; break; + case "s": argsSpts[a] = $"strftime({left},'%-S')"; break; + case "tt": argsSpts[a] = $"strftime({left},'%p')"; break; + case "t": argsSpts[a] = $"substr(strftime({left},'%p'),1,1)"; break; + default: + var argsSptsA = argsSpts[a]; + if (argsSptsA.StartsWith("'")) argsSptsA = argsSptsA.Substring(1); + if (argsSptsA.EndsWith("'")) argsSptsA = argsSptsA.Remove(argsSptsA.Length - 1); + argsSpts[a] = argsFinds.Any(m => argsSptsA.Contains(m)) ? $"strftime({left},'{argsSptsA}')" : $"'{argsSptsA}'"; + break; + } + } + if (argsSpts.Length > 0) args1 = $"({string.Join(" || ", argsSpts.Where(a => a != "''"))})"; + return args1.Replace("%_a1", "%m").Replace("%_a2", "%d").Replace("%_a3", "%H").Replace("%_a4", "%M"); + } + } + return null; + } + public override string ExpressionLambdaToSqlCallConvert(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + case "ToBoolean": return $"({getExp(exp.Arguments[0])} not in ('0','false'))"; + case "ToByte": return $"cast({getExp(exp.Arguments[0])} as utinyint)"; + case "ToChar": return $"substr(cast({getExp(exp.Arguments[0])} as char), 1, 1)"; + case "ToDateTime": return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; + case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as decimal(36,18))"; + case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as double)"; + case "ToInt16": return $"cast({getExp(exp.Arguments[0])} as smallint)"; + case "ToInt32": return $"cast({getExp(exp.Arguments[0])} as integer)"; + case "ToInt64": return $"cast({getExp(exp.Arguments[0])} as bigint)"; + case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as tinyint)"; + case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as float)"; + case "ToString": return $"cast({getExp(exp.Arguments[0])} as text)"; + case "ToUInt16": return $"cast({getExp(exp.Arguments[0])} as usmallint)"; + case "ToUInt32": return $"cast({getExp(exp.Arguments[0])} as uinteger)"; + case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as ubigint)"; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineExtensions.cs b/Providers/FreeSql.Provider.TDengine/TDengineExtensions.cs new file mode 100644 index 000000000..ef7b40586 --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineExtensions.cs @@ -0,0 +1,15 @@ +using System.Text; +using FreeSql.TDengine; + +public static class FreeSqlTDengineGlobalExtensions +{ + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatTDengine(this string that, params object[] args) => TDengineAdo.Addslashes(that, args); + + static readonly TDengineAdo TDengineAdo = new TDengineAdo(); +} \ 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..ddb8c64fc --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineProvider.cs @@ -0,0 +1,100 @@ +using FreeSql.Internal.CommonProvider; +using System; +using System.Data.Common; +using System.Linq; +using System.Reflection; +using System.Threading; +using FreeSql.Aop; +using FreeSql.Provider.TDengine.Attributes; +using FreeSql.TDengine.Curd; +using Newtonsoft.Json.Linq; +using TDengine.Data.Client; + +namespace FreeSql.TDengine +{ + internal class TDengineProvider : BaseDbProvider, IFreeSql + { + public TDengineProvider(string masterConnectionString, string[] slaveConnectionString, + Func connectionFactory = null) + { + this.InternalCommonUtils = new TDengineUtils(this); + this.InternalCommonExpression = new TDengineExpression(this.InternalCommonUtils); + this.Ado = new TDengineAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString, + connectionFactory); + this.Aop = new AopProvider(); + this.DbFirst = new TDengineDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.CodeFirst = new TDengineCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + + //处理超级表查询问题 + //this.Aop.ConfigEntityProperty += (s, e) => + //{ + // if (e.Property.ReflectedType == null) + // return; + + // if (e.Property.ReflectedType.BaseType == null) + // return; + + // var propertyInfo = e.Property.ReflectedType.BaseType.GetProperty(e.Property.Name); + + // if (propertyInfo == null) + // return; + + // if (propertyInfo.GetCustomAttribute(typeof(TDengineTagAttribute)) != null) + // e.ModifyResult.IsIgnore = true; + //}; + + //处理参数化 + this.Aop.CommandBefore += (_, e) => + { + if (e.Command.Parameters.Count <= 0) return; + var dengineParameters = new TDengineParameter[e.Command.Parameters.Count]; + e.Command.Parameters.CopyTo(dengineParameters, 0); + var cmdText = e.Command.CommandText; + var isChanged = false; + foreach (var parameter in dengineParameters.OrderByDescending(a => a.ParameterName.Length)) + { + var idx = cmdText.IndexOf(parameter.ParameterName, StringComparison.Ordinal); + if (idx != -1) + { + isChanged = true; + cmdText = + $"{cmdText.Substring(0, idx)}?{cmdText.Substring(idx + parameter.ParameterName.Length)}"; + } + } + + if (isChanged) e.Command.CommandText = cmdText; + }; + } + + public override ISelect CreateSelectProvider(object dywhere) => + new TDengineSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public override IInsert CreateInsertProvider() => + new TDengineInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + + + public override IUpdate CreateUpdateProvider(object dywhere) + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + } + + public override IDelete CreateDeleteProvider(object dywhere) + { + return new TDengineDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + } + + public override IInsertOrUpdate CreateInsertOrUpdateProvider() + { + throw new NotImplementedException($"FreeSql.Provider.TDengine {CoreErrorStrings.S_Not_Implemented_Feature}"); + } + + ~TDengineProvider() => this.Dispose(); + int _disposeCounter; + + public override void Dispose() + { + if (Interlocked.Increment(ref _disposeCounter) != 1) return; + (this.Ado as AdoProvider)?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/TDengineUtils.cs b/Providers/FreeSql.Provider.TDengine/TDengineUtils.cs new file mode 100644 index 000000000..282dd33dd --- /dev/null +++ b/Providers/FreeSql.Provider.TDengine/TDengineUtils.cs @@ -0,0 +1,237 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using FreeSql.DataAnnotations; +using FreeSql.TDengine.Describes; +using TDengine.Data.Client; +using TDengine.Driver; + +namespace FreeSql.TDengine +{ + internal class TDengineUtils : CommonUtils + { + public TDengineUtils(IFreeSql orm) : base(orm) + { + } + + public override string Now => "now()"; + + public override string NowUtc => throw new NotImplementedException(); + + public override DbParameter AppendParamter(List _params, string parameterName, ColumnInfo col, + Type type, object value) + { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + if (value != null) value = getParamterValue(type, value); + var ret = new TDengineParameter() { ParameterName = QuoteParamterName(parameterName), Value = value }; + var dbType = _orm.DbFirst.GetDbType(new DatabaseModel.DbColumnInfo + { DbTypeText = col.DbTypeText, DbTypeTextFull = col.Attribute.DbType, MaxLength = col.DbSize }); + ret.DbType = (DbType)dbType; + _params?.Add(ret); + return ret; + } + + public override string Div(string left, string right, Type leftType, Type rightType) + { + throw new NotImplementedException(); + } + + public override string FormatSql(string sql, params object[] args) => sql.FormatTDengine(args); + + static Dictionary> dicGetParamterValue = + new Dictionary> + { + { typeof(uint).FullName, a => long.Parse(string.Concat(a)) }, + { typeof(uint[]).FullName, a => getParamterArrayValue(typeof(long), a, 0) }, + { typeof(uint?[]).FullName, a => getParamterArrayValue(typeof(long?), a, null) }, + { typeof(ulong).FullName, a => decimal.Parse(string.Concat(a)) }, + { typeof(ulong[]).FullName, a => getParamterArrayValue(typeof(decimal), a, 0) }, + { typeof(ulong?[]).FullName, a => getParamterArrayValue(typeof(decimal?), a, null) }, + { typeof(ushort).FullName, a => int.Parse(string.Concat(a)) }, + { typeof(ushort[]).FullName, a => getParamterArrayValue(typeof(int), a, 0) }, + { typeof(ushort?[]).FullName, a => getParamterArrayValue(typeof(int?), a, null) }, + { typeof(byte).FullName, a => short.Parse(string.Concat(a)) }, + { typeof(byte[]).FullName, a => getParamterArrayValue(typeof(short), a, 0) }, + { typeof(byte?[]).FullName, a => getParamterArrayValue(typeof(short?), a, null) }, + { typeof(sbyte).FullName, a => short.Parse(string.Concat(a)) }, + { typeof(sbyte[]).FullName, a => getParamterArrayValue(typeof(short), a, 0) }, + { typeof(sbyte?[]).FullName, a => getParamterArrayValue(typeof(short?), a, null) }, + { typeof(char).FullName, a => string.Concat(a).Replace('\0', ' ').ToCharArray().FirstOrDefault() }, + { + typeof(BigInteger).FullName, + a => BigInteger.Parse(string.Concat(a), System.Globalization.NumberStyles.Any) + }, + { typeof(BigInteger[]).FullName, a => getParamterArrayValue(typeof(BigInteger), a, 0) }, + { typeof(BigInteger?[]).FullName, a => getParamterArrayValue(typeof(BigInteger?), a, null) }, + }; + + static Array getParamterArrayValue(Type arrayType, object value, object defaultValue) + { + var valueArr = value as Array; + var len = valueArr.GetLength(0); + var ret = Array.CreateInstance(arrayType, len); + for (var a = 0; a < len; a++) + { + var item = valueArr.GetValue(a); + ret.SetValue(item == null ? defaultValue : getParamterValue(item.GetType(), item, 1), a); + } + + return ret; + } + + static object getParamterValue(Type type, object value, int level = 0) + { + if (type.FullName == "System.Byte[]") return value; + if (type.FullName == "System.Char[]") return value; + if (type.IsArray && level == 0) + { + var elementType = type.GetElementType(); + Type enumType = null; + if (elementType.IsEnum) enumType = elementType; + else if (elementType.IsNullableType() && elementType.GenericTypeArguments.First().IsEnum) + enumType = elementType.GenericTypeArguments.First(); + if (enumType != null) + return enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() + ? getParamterArrayValue(typeof(long), value, + elementType.IsEnum ? null : enumType.CreateInstanceGetDefaultValue()) + : getParamterArrayValue(typeof(int), value, + elementType.IsEnum ? null : enumType.CreateInstanceGetDefaultValue()); + return dicGetParamterValue.TryGetValue(type.FullName, out var trydicarr) ? trydicarr(value) : value; + } + + if (type.IsNullableType()) type = type.GenericTypeArguments.First(); + if (type.IsEnum) return (int)value; + if (dicGetParamterValue.TryGetValue(type.FullName, out var trydic)) return trydic(value); + return value; + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) + => + Utils.GetDbParamtersByObject(sql, obj, "@", (name, type, value) => + { + if (value != null) value = getParamterValue(type, value); + var ret = new TDengineParameter { ParameterName = $"@{name}", Value = value }; + //if (value.GetType().IsEnum || value.GetType().GenericTypeArguments.FirstOrDefault()?.IsEnum == true) { + // ret.DataTypeName = ""; + //} else { + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.DbType = (DbType)tp.Value; + //} + return ret; + }); + + public override string GetNoneParamaterSqlValue(List specialParams, string specialParamFlag, + ColumnInfo col, Type type, object value) + { + if (value == null) return "NULL"; + if (type.IsNumberType()) return string.Format(CultureInfo.InvariantCulture, "{0}", value); + if (type == typeof(byte[])) return $"0x{CommonUtils.BytesSqlRaw(value as byte[])}"; + if (type == typeof(TimeSpan) || type == typeof(TimeSpan?)) + { + var ts = (TimeSpan)value; + value = $"{Math.Floor(ts.TotalHours)}:{ts.Minutes}:{ts.Seconds}"; + } + + return FormatSql("{0}", value, 1); + } + + public override string RewriteColumn(ColumnInfo col, string sql) + { + if (string.IsNullOrWhiteSpace(col?.Attribute.RewriteSql) == false) + return string.Format(col.Attribute.RewriteSql, sql); + + return sql; + } + + public override string IsNull(string sql, object value) + { + throw new NotImplementedException(); + } + + public override string Mod(string left, string right, Type leftType, Type rightType) + { + throw new NotImplementedException(); + } + + public override string QuoteParamterName(string name) => $"@{name}"; + + public override string QuoteSqlNameAdapter(params string[] name) + { + if (name.Length == 1) + { + var nameAdapter = name[0].Trim(); + if (nameAdapter.StartsWith("(") && nameAdapter.EndsWith(")")) + return nameAdapter; //原生SQL + if (nameAdapter.StartsWith("`") && nameAdapter.EndsWith("`")) + return nameAdapter; + return $"`{nameAdapter.Replace(".", "`.`")}`"; + } + + return $"`{string.Join("`.`", name)}`"; + } + + public override string QuoteWriteParamterAdapter(Type type, string paramterName) => paramterName; + + public override string[] SplitTableName(string name) + { + return new[] { name }; + } + + public override string StringConcat(string[] objs, Type[] types) + { + throw new NotImplementedException(); + } + + public override string TrimQuoteSqlName(string name) + { + var nametrim = name.Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + return $"{nametrim.Trim('`').Replace("`.`", ".").Replace(".`", ".")}"; + } + + protected override string QuoteReadColumnAdapter(Type type, Type mapType, string columnName) + { + return columnName; + } + + //超表描述 + private static readonly ConcurrentDictionary> STableDescribes = + new ConcurrentDictionary>(); + + /// + /// 通过子表获取超级表描述 + /// + /// + /// + internal SuperTableDescribe GetSuperTableDescribe(Type subTableType) + { + var stableDescribe = STableDescribes.GetOrAdd(subTableType, key => new Lazy(() => + { + var sTableAttribute = subTableType.GetCustomAttribute(); + if (sTableAttribute == null) return null; + var describe = new SuperTableDescribe + { + SuperTableName = sTableAttribute.SuperTableName, + SuperTableType = subTableType.BaseType + }; + return describe; + })); + + var stableDescribeValue = stableDescribe.Value; + + return stableDescribeValue; + } + + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.TDengine/key.snk b/Providers/FreeSql.Provider.TDengine/key.snk new file mode 100644 index 000000000..be1149357 Binary files /dev/null and b/Providers/FreeSql.Provider.TDengine/key.snk differ