From d085acc4e959b7af3b98287bac46f6bd4f0d421a Mon Sep 17 00:00:00 2001 From: chenbo <1114927184@qq.com> Date: Thu, 25 Nov 2021 17:40:00 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ClickHouse=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Extensions/FreeSql.Generator/ConsoleApp.cs | 1 + FreeSql.DbContext/FreeSql.DbContext.xml | 9 + .../ClickHouse/ClickHouseTest1.cs | 95 +++ .../FreeSql.Tests/FreeSql.Tests.csproj | 1 + FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml | 10 +- FreeSql.Tests/FreeSql.Tests/UnitTest5.cs | 23 + FreeSql.Tests/FreeSql.Tests/g.cs | 13 + FreeSql.sln | 19 +- FreeSql/DataType.cs | 3 +- FreeSql/Extensions/AdoNetExtensions.cs | 4 + FreeSql/Extensions/FreeSqlGlobalExtensions.cs | 2 + FreeSql/FreeSql.csproj | 2 +- FreeSql/FreeSqlBuilder.cs | 5 + .../Internal/CommonProvider/DeleteProvider.cs | 2 +- .../Internal/CommonProvider/UpdateProvider.cs | 2 +- FreeSql/Internal/CommonUtils.cs | 2 +- .../ClickHouseAdo/ClickHouseAdo.cs | 79 +++ .../ClickHouseAdo/ClickHouseConnectionPool.cs | 257 ++++++++ .../ClickHouseCodeFirst.cs | 371 ++++++++++++ .../ClickHouseDbFirst.cs | 445 ++++++++++++++ .../ClickHouseExpression.cs | 559 ++++++++++++++++++ .../ClickHouseExtensions.cs | 18 + .../ClickHouseProvider.cs | 42 ++ .../ClickHouseUtils.cs | 133 +++++ .../Curd/ClickHouseDelete.cs | 35 ++ .../Curd/ClickHouseInsert.cs | 178 ++++++ .../Curd/ClickHouseSelect.cs | 210 +++++++ .../Curd/ClickHouseUpdate.cs | 79 +++ .../FreeSql.Provider.ClickHouse.csproj | 44 ++ 29 files changed, 2631 insertions(+), 12 deletions(-) create mode 100644 FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseConnectionPool.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseProvider.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseDelete.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs create mode 100644 Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj diff --git a/Extensions/FreeSql.Generator/ConsoleApp.cs b/Extensions/FreeSql.Generator/ConsoleApp.cs index 178df09f..e51a1429 100644 --- a/Extensions/FreeSql.Generator/ConsoleApp.cs +++ b/Extensions/FreeSql.Generator/ConsoleApp.cs @@ -180,6 +180,7 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat,双击它重新 case "dameng": ArgsDbType = DataType.Dameng; break; case "kingbasees": ArgsDbType = DataType.KingbaseES; break; case "shentong": ArgsDbType = DataType.ShenTong; break; + case "clickhouse": ArgsDbType = DataType.ClickHouse; break; default: throw new ArgumentException($"-DB 参数错误,不支持的类型:\"{dbargs[0]}\""); } ArgsConnectionString = dbargs[1].Trim(); diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index da7ace6b..bdd16ff9 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -538,5 +538,14 @@ + + + 批量注入 Repository,可以参考代码自行调整 + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs new file mode 100644 index 00000000..439b60c7 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Xunit; + +namespace FreeSql.Tests.MySql +{ + public class ClickHouseTest1 + { + + class TestAuditValue + { + public Guid id { get; set; } + [Now] + public DateTime createtime { get; set; } + } + [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] + public class TestClickHouse + { + public long Id { get; set; } + + public string Name { get; set; } + } + class NowAttribute: Attribute { } + + [Fact] + public void AuditValue() + { + var date = DateTime.Now.Date; + var item = new TestAuditValue(); + + EventHandler audit = (s, e) => + { + if (e.Property.GetCustomAttribute(false) != null) + e.Value = DateTime.Now.Date; + }; + g.mysql.Aop.AuditValue += audit; + + g.mysql.Insert(item).ExecuteAffrows(); + + g.mysql.Aop.AuditValue -= audit; + + Assert.Equal(item.createtime, date); + } + + [Fact] + public void TestInsert() + { + var fsql = g.clickHouse; + List list=new List(); + for (int i = 0; i < 1000; i++) + { + list.Add(new TestClickHouse() + { + Id = i, + Name = $"测试{i}" + }); + } + fsql.Insert(list).ExecuteAffrows(); + var items = fsql.Select().Where(o=>o.Id>900).OrderByDescending(o=>o.Id).ToList(); + Assert.Equal(100, items.Count); + } + [Fact] + public void TestPage() + { + var fsql = g.clickHouse; + + var list=fsql.Select() + .Page(1,100) + .Where(o=>o.Id>200&&o.Id<500) + .Count(out var count).ToList(); + Assert.Equal(100, list.Count); + } + [Fact] + public void TestDelete() + { + var fsql = g.clickHouse; + var count1=fsql.Select().Count(); + fsql.Delete().Where(o => o.Id < 500).ExecuteAffrows(); + var count2 = fsql.Select().Count(); + Assert.NotEqual(count1, count2); + } + [Fact] + public void TestUpdate() + { + var fsql = g.clickHouse; + fsql.Update().Where(o => o.Id > 900) + .Set(o=>o.Name,"修改后的值") + .ExecuteAffrows(); + + } + + } +} diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj index 37eba2fa..8f797408 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj @@ -37,6 +37,7 @@ + diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml index c56f837d..2f9add59 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml @@ -4,6 +4,11 @@ FreeSql.Tests + + + 保存或添加,如果主键有值则尝试 Update,如果影响的行为 0 则尝试 Insert + + 编号 @@ -287,11 +292,6 @@ 修改人 - - - 保存或添加,如果主键有值则尝试 Update,如果影响的行为 0 则尝试 Insert - - 表中带点 diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest5.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest5.cs index 90f19686..59d893bc 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest5.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest5.cs @@ -34,6 +34,29 @@ namespace FreeSql.Tests var items = fsql.Select().ToList(); } + [Fact] + public void TestClickHouse() + { + var fsql = g.mysql; + fsql.Delete().Where("1=1").ExecuteAffrows(); + + var item = new TestJsonb01Cls1 + { + jsonb01 = new List { 1, 5, 10, 20 }, + jsonb02 = new List { 11, 51, 101, 201 }, + jsonb03 = new List { "12", "52", "102", "202" }, + }; + fsql.Insert(item).ExecuteAffrows(); + + } + [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] + public class ClickHouse + { + public long Id { get; set; } + + public string Name { get; set; } + } + public class TestJsonb01Cls1 { public Guid id { get; set; } diff --git a/FreeSql.Tests/FreeSql.Tests/g.cs b/FreeSql.Tests/FreeSql.Tests/g.cs index 32959df7..8fca5b2a 100644 --- a/FreeSql.Tests/FreeSql.Tests/g.cs +++ b/FreeSql.Tests/FreeSql.Tests/g.cs @@ -7,6 +7,19 @@ using System.Threading; public class g { + static Lazy clickHouseLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.ClickHouse, "Compress=False;BufferSize=32768;SocketTimeout=10000;CheckCompressedHash=False;Encrypt=False;Compressor=lz4;Host=192.168.0.121;Port=8125;Database=PersonnelLocation;Username=root;Password=+riQ8V9D") + //.UseConnectionFactory(FreeSql.DataType.MySql, () => new MySql.Data.MySqlClient.MySqlConnection("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;")) + //.UseConnectionString(FreeSql.DataType.MySql, "Data Source=192.168.164.10;Port=33061;User ID=root;Password=root;Initial Catalog=cccddd_mysqlconnector;Charset=utf8;SslMode=none;Max pool size=10") + //.UseAutoSyncStructure(true) + //.UseGenerateCommandParameterWithLambda(true) + .UseMonitorCommand( + cmd => Trace.WriteLine("\r\n线程" + Thread.CurrentThread.ManagedThreadId + ": " + cmd.CommandText) //监听SQL命令对象,在执行前 + //, (cmd, traceLog) => Console.WriteLine(traceLog) + ) + .UseLazyLoading(true) + .Build()); + public static IFreeSql clickHouse => clickHouseLazy.Value; static Lazy mysqlLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=5;Allow User Variables=True") diff --git a/FreeSql.sln b/FreeSql.sln index 6849c8a8..a6d59db1 100644 --- a/FreeSql.sln +++ b/FreeSql.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29324.140 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql", "FreeSql\FreeSql.csproj", "{AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}" EndProject @@ -101,6 +101,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.zh-CN.md = README.zh-CN.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeSql.Provider.ClickHouse", "Providers\FreeSql.Provider.ClickHouse\FreeSql.Provider.ClickHouse.csproj", "{86C56235-5D37-4422-807B-B31681C7D01C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -579,6 +581,18 @@ Global {0DBAA21C-39B2-4AAD-A43D-88E67ED239D1}.Release|x64.Build.0 = Release|Any CPU {0DBAA21C-39B2-4AAD-A43D-88E67ED239D1}.Release|x86.ActiveCfg = Release|Any CPU {0DBAA21C-39B2-4AAD-A43D-88E67ED239D1}.Release|x86.Build.0 = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|x64.ActiveCfg = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|x64.Build.0 = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|x86.ActiveCfg = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Debug|x86.Build.0 = Debug|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|Any CPU.Build.0 = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|x64.ActiveCfg = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|x64.Build.0 = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|x86.ActiveCfg = Release|Any CPU + {86C56235-5D37-4422-807B-B31681C7D01C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -612,6 +626,7 @@ Global {CDD6A896-F6DF-44CB-B430-06B383916EB0} = {2A381C57-2697-427B-9F10-55DA11FD02E4} {101B11D2-7780-4E14-9B72-77F5D69B3DF9} = {2A381C57-2697-427B-9F10-55DA11FD02E4} {0DBAA21C-39B2-4AAD-A43D-88E67ED239D1} = {2A381C57-2697-427B-9F10-55DA11FD02E4} + {86C56235-5D37-4422-807B-B31681C7D01C} = {2A381C57-2697-427B-9F10-55DA11FD02E4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {089687FD-5D25-40AB-BA8A-A10D1E137F98} diff --git a/FreeSql/DataType.cs b/FreeSql/DataType.cs index fb574578..f2c978ea 100644 --- a/FreeSql/DataType.cs +++ b/FreeSql/DataType.cs @@ -60,6 +60,7 @@ namespace FreeSql /// 自定义适配器,访问任何数据库 /// 注意:该类型不提供 DbFirst/CodeFirst 功能 /// - Custom + Custom, + ClickHouse } } diff --git a/FreeSql/Extensions/AdoNetExtensions.cs b/FreeSql/Extensions/AdoNetExtensions.cs index 3e27483b..73b0d015 100644 --- a/FreeSql/Extensions/AdoNetExtensions.cs +++ b/FreeSql/Extensions/AdoNetExtensions.cs @@ -59,6 +59,10 @@ namespace FreeSql providerType = Type.GetType("FreeSql.Firebird.FirebirdProvider`1,FreeSql.Provider.Firebird")?.MakeGenericType(connType); if (providerType == null) throw new Exception("缺少 FreeSql 数据库实现包:FreeSql.Provider.Firebird.dll,可前往 nuget 下载"); break; + case "ClickHouseConnection": + providerType = Type.GetType("FreeSql.ClickHouse.ClickHouseProvider`1,FreeSql.Provider.ClickHouse")?.MakeGenericType(connType); + if (providerType == null) throw new Exception("缺少 FreeSql 数据库实现包:FreeSql.Provider.ClickHouse.dll,可前往 nuget 下载"); + break; default: throw new Exception("未实现"); } diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index b96ea0c2..25da334b 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -495,6 +495,7 @@ JOIN {select._commonUtils.QuoteSqlName(tb.DbName)} a ON cte_tbc.cte_id = a.{sele case DataType.SqlServer: case DataType.OdbcSqlServer: case DataType.Firebird: + case DataType.ClickHouse: sql1ctePath = select._commonExpression.ExpressionWhereLambda(select._tables, Expression.Call(typeof(Convert).GetMethod("ToString", new Type[] { typeof(string) }), pathSelector?.Body), null, null, null); break; default: @@ -609,6 +610,7 @@ SELECT "); { case DataType.MySql: case DataType.OdbcMySql: + case DataType.ClickHouse: return that.OrderBy("rand()"); case DataType.SqlServer: case DataType.OdbcSqlServer: diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj index 40d58264..4d45126e 100644 --- a/FreeSql/FreeSql.csproj +++ b/FreeSql/FreeSql.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net45;net40 + netstandard2.1;netstandard2.0;net45;net40 2.6.100 true FreeSql;ncc;YeXiangQin diff --git a/FreeSql/FreeSqlBuilder.cs b/FreeSql/FreeSqlBuilder.cs index 344e2a2c..0bb2383f 100644 --- a/FreeSql/FreeSqlBuilder.cs +++ b/FreeSql/FreeSqlBuilder.cs @@ -265,6 +265,11 @@ namespace FreeSql if (type == null) throwNotFind("FreeSql.Provider.Custom.dll", "FreeSql.Custom.CustomProvider<>"); break; + case DataType.ClickHouse: + type = Type.GetType("FreeSql.ClickHouse.ClickHouseProvider`1,FreeSql.Provider.ClickHouse")?.MakeGenericType(typeof(TMark)); + if (type == null) throwNotFind("FreeSql.Provider.ClickHouse.dll", "FreeSql.ClickHouse.ClickHouseProvider<>"); + break; + default: throw new Exception("未指定 UseConnectionString 或者 UseConnectionFactory"); } } diff --git a/FreeSql/Internal/CommonProvider/DeleteProvider.cs b/FreeSql/Internal/CommonProvider/DeleteProvider.cs index 95d92df9..652623db 100644 --- a/FreeSql/Internal/CommonProvider/DeleteProvider.cs +++ b/FreeSql/Internal/CommonProvider/DeleteProvider.cs @@ -157,7 +157,7 @@ namespace FreeSql.Internal.CommonProvider return this; } - public string ToSql() + public virtual string ToSql() { if (_whereTimes <= 0) return null; var sb = new StringBuilder().Append("DELETE FROM ").Append(_commonUtils.QuoteSqlName(TableRuleInvoke())).Append(" WHERE ").Append(_where); diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs index 2fad2154..e4250ebe 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProvider.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -445,7 +445,7 @@ namespace FreeSql.Internal.CommonProvider _set.Append(", ").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = "); var colsql = _noneParameter ? _commonUtils.GetNoneParamaterSqlValue(_params, "u", col, col.Attribute.MapType, val) : - _commonUtils.QuoteWriteParamterAdapter(col.Attribute.MapType, $"{_commonUtils.QuoteParamterName("p_")}{_params.Count}"); + _commonUtils.QuoteWriteParamterAdapter(col.Attribute.MapType, _commonUtils.QuoteParamterName($"p_{_params.Count}")); _set.Append(_commonUtils.RewriteColumn(col, colsql)); if (_noneParameter == false) _commonUtils.AppendParamter(_params, null, col, col.Attribute.MapType, val); diff --git a/FreeSql/Internal/CommonUtils.cs b/FreeSql/Internal/CommonUtils.cs index 63bd8905..93be8c72 100644 --- a/FreeSql/Internal/CommonUtils.cs +++ b/FreeSql/Internal/CommonUtils.cs @@ -54,7 +54,7 @@ namespace FreeSql.Internal public abstract string NowUtc { get; } public abstract string QuoteWriteParamterAdapter(Type type, string paramterName); protected abstract string QuoteReadColumnAdapter(Type type, Type mapType, string columnName); - public string RewriteColumn(ColumnInfo col, string sql) + public virtual string RewriteColumn(ColumnInfo col, string sql) { if (string.IsNullOrWhiteSpace(col?.Attribute.RewriteSql) == false) return string.Format(col.Attribute.RewriteSql, sql); diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs new file mode 100644 index 00000000..cbd805ef --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs @@ -0,0 +1,79 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections; +using System.Data.Common; +using System.Text; +using System.Threading; +using ClickHouse.Client.ADO; + +namespace FreeSql.ClickHouse +{ + class ClickHouseAdo : FreeSql.Internal.CommonProvider.AdoProvider + { + + public ClickHouseAdo() : base(DataType.ClickHouse, null, null) { } + public ClickHouseAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, Func connectionFactory) : base(DataType.MySql, masterConnectionString, slaveConnectionStrings) + { + base._util = util; + if (connectionFactory != null) + { + MasterPool = new FreeSql.Internal.CommonProvider.DbConnectionPool(DataType.ClickHouse, connectionFactory); + return; + } + if (!string.IsNullOrEmpty(masterConnectionString)) + MasterPool = new ClickHouseConnectionPool("主库", masterConnectionString, null, null); + if (slaveConnectionStrings != null) + { + foreach (var slaveConnectionString in slaveConnectionStrings) + { + var slavePool = new ClickHouseConnectionPool($"从库{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 || param is bool?) + return (bool)param ? 1 : 0; + else if (param is string) + return string.Concat("'", param.ToString().Replace("'", "''").Replace("\\", "\\\\"), "'"); //只有 mysql 需要处理反斜杠 + else if (param is char) + return string.Concat("'", param.ToString().Replace("'", "''").Replace("\\", "\\\\").Replace('\0', ' '), "'"); + else if (param is Enum) + return string.Concat("'", param.ToString().Replace("'", "''").Replace("\\", "\\\\"), "'"); //((Enum)val).ToInt64(); + else if (decimal.TryParse(string.Concat(param), out var trydec)) + return param; + else if (param is DateTime || param is DateTime?) + return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.fff"), "'"); + else if (param is TimeSpan || param is TimeSpan?) + return ((TimeSpan)param).Ticks / 10; + else if (param is byte[]) + return $"0x{CommonUtils.BytesSqlRaw(param as byte[])}"; + else if (param is IEnumerable) + return AddslashesIEnumerable(param, mapType, mapColumn); + + return string.Concat("'", param.ToString().Replace("'", "''").Replace("\\", "\\\\"), "'"); + } + + public override DbCommand CreateCommand() + { + System.Data.IDbCommand command = new ClickHouseCommand(); + return (DbCommand)command; + } + + public override void ReturnConnection(IObjectPool pool, Object conn, Exception ex) + { + var rawPool = pool as ClickHouseConnectionPool; + if (rawPool != null) rawPool.Return(conn, ex); + else pool.Return(conn); + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseConnectionPool.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseConnectionPool.cs new file mode 100644 index 00000000..b217c586 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseConnectionPool.cs @@ -0,0 +1,257 @@ +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using ClickHouse.Client.ADO; + +namespace FreeSql.ClickHouse +{ + + class ClickHouseConnectionPool : ObjectPool + { + + internal Action availableHandler; + internal Action unavailableHandler; + + public ClickHouseConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) + { + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + var policy = new ClickHouseConnectionPoolPolicy + { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) + { + if (exception != null && exception is ClickHouseException) + { + try { if (obj.Value.Ping() == false) obj.Value.Open(); } catch { base.SetUnavailable(exception); } + } + base.Return(obj, isRecreate); + } + } + public class ClickHouseException : Exception + { + public int Code; + + public string Name; + public string ServerStackTrace; + + public ClickHouseException() { } + + public ClickHouseException(string message) : base(message) { } + + public ClickHouseException(string message, Exception innerException) : base(message, innerException) { } + } + class ClickHouseConnectionPoolPolicy : IPolicy + { + + internal ClickHouseConnectionPool _pool; + public string Name { get; set; } = "ClickHouse ClickHouseConnection 对象池"; + public int PoolSize { get; set; } = 100; + 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; } = 5; + + static ConcurrentDictionary dicConnStrIncr = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + private string _connectionString; + public string ConnectionString + { + get => _connectionString; + set + { + _connectionString = value ?? ""; + + //var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)"; + //var m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + //if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100; + //var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => Math.Min(5, oldval + 1)); + //PoolSize = poolsize + connStrIncr; + //_connectionString = m.Success ? + // Regex.Replace(_connectionString, pattern, $"Max pool size={PoolSize}", RegexOptions.IgnoreCase) : + // $"{_connectionString};Max pool size={PoolSize}"; + + + var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)"; + Match m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + PoolSize = int.Parse(m.Groups[1].Value); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + 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); + } + + var minPoolSize = 0; + pattern = @"Min\s*pool\s*size\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + minPoolSize = int.Parse(m.Groups[1].Value); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize); + } + } + + public bool OnCheckAvailable(Object obj) + { + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + return obj.Value.Ping(true); + } + + public DbConnection OnCreate() + { + var conn = new ClickHouseConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) + { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGet(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true) + throw new Exception($"【{this.Name}】连接字符串错误,请检查。"); + return; + } + + 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 (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + +#if net40 +#else + async public Task OnGetAsync(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true) + throw new Exception($"【{this.Name}】连接字符串错误,请检查。"); + return; + } + + 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 (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } +#endif + + public void OnGetTimeout() + { + + } + + public void OnReturn(Object obj) + { + //if (obj?.Value != null && obj.Value.State != ConnectionState.Closed) try { obj.Value.Close(); } catch { } + } + + public void OnAvailable() + { + _pool.availableHandler?.Invoke(); + } + + public void OnUnavailable() + { + _pool.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 { } + if (isThrow) throw; + return false; + } + } + +#if net40 +#else + async public static 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 + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs new file mode 100644 index 00000000..09b7003e --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs @@ -0,0 +1,371 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Data.Common; +using FreeSql.Internal.ObjectPool; +using ClickHouse.Client.ADO; + +namespace FreeSql.ClickHouse +{ + + class ClickHouseCodeFirst : Internal.CommonProvider.CodeFirstProvider + { + + public ClickHouseCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { } + + static object _dicCsToDbLock = new object(); + + static Dictionary> _dicCsToDb = new Dictionary>() { + { typeof(bool).FullName, CsToDb.New(DbType.SByte, "Int8","Int8", null, false, false) },{ typeof(bool?).FullName, CsToDb.New(DbType.SByte, "Int8","Nullable(Int8)", null, true, null) }, + + { typeof(sbyte).FullName, CsToDb.New(DbType.SByte, "Int8", "Int8", false, false, 0) },{ typeof(sbyte?).FullName, CsToDb.New(DbType.SByte, "Int8", "Nullable(Int8)", false, true, null) }, + { typeof(short).FullName, CsToDb.New(DbType.Int16, "Int16","Int16", false, false, 0) },{ typeof(short?).FullName, CsToDb.New(DbType.Int16, "Int16", "Nullable(Int16)", false, true, null) }, + { typeof(int).FullName, CsToDb.New(DbType.Int32, "Int32", "Int32", false, false, 0) },{ typeof(int?).FullName, CsToDb.New(DbType.Int32, "Int32", "Nullable(Int32)", false, true, null) }, + { typeof(long).FullName, CsToDb.New(DbType.Int64, "Int64","Int64", false, false, 0) },{ typeof(long?).FullName, CsToDb.New(DbType.Int64, "Int64","Nullable(Int64)", false, true, null) }, + + { typeof(byte).FullName, CsToDb.New(DbType.Byte, "UInt8","UInt8", true, false, 0) },{ typeof(byte?).FullName, CsToDb.New(DbType.Byte, "UInt8","Nullable(UInt8)", true, true, null) }, + { typeof(ushort).FullName, CsToDb.New(DbType.UInt16, "UInt16","UInt16", true, false, 0) },{ typeof(ushort?).FullName, CsToDb.New(DbType.UInt16, "UInt16", "Nullable(UInt16)", true, true, null) }, + { typeof(uint).FullName, CsToDb.New(DbType.UInt32, "UInt32", "UInt32", true, false, 0) },{ typeof(uint?).FullName, CsToDb.New(DbType.UInt32, "UInt32", "Nullable(UInt32)", true, true, null) }, + { typeof(ulong).FullName, CsToDb.New(DbType.UInt64, "UInt64", "UInt64", true, false, 0) },{ typeof(ulong?).FullName, CsToDb.New(DbType.UInt64, "UInt64", "Nullable(UInt64)", true, true, null) }, + + { typeof(double).FullName, CsToDb.New(DbType.Double, "Float64", "Float64", false, false, 0) },{ typeof(double?).FullName, CsToDb.New(DbType.Double, "Float64", "Nullable(Float64)", false, true, null) }, + { typeof(float).FullName, CsToDb.New(DbType.Single, "Float32","Float32", false, false, 0) },{ typeof(float?).FullName, CsToDb.New(DbType.Single, "Float32","Nullable(Float32)", false, true, null) }, + + { typeof(DateTime).FullName, CsToDb.New(DbType.DateTime, "DateTime('Asia/Shanghai')", "DateTime('Asia/Shanghai')", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName, CsToDb.New(DbType.DateTime, "DateTime('Asia/Shanghai')", "Nullable(DateTime('Asia/Shanghai'))", false, true, null) }, + + { typeof(string).FullName, CsToDb.New(DbType.String, "String", "String", false, null, "") }, + { typeof(char).FullName, CsToDb.New(DbType.String, "String", "String", false, false, "") },{ typeof(char?).FullName, CsToDb.New(DbType.Single, "String","Nullable(String)", false, true, null) }, + { typeof(Guid).FullName, CsToDb.New(DbType.String, "String", "String", false, false, Guid.Empty) },{ typeof(Guid?).FullName, CsToDb.New(DbType.String, "String", "Nullable(String)", false, true, null) }, + + }; + + 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 TypeAndName[] objects) + { + Object conn = null; + string database = null; + + try + { + conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5)); + database = conn.Value.Database; + + var sb = new StringBuilder(); + foreach (var obj in objects) + { + if (sb.Length > 0) sb.Append("\r\n"); + var tb = _commonUtils.GetTableByEntity(obj.entityType); + if (tb == null) throw new Exception($"类型 {obj.entityType.FullName} 不可迁移"); + if (tb.Columns.Any() == false) throw new Exception($"类型 {obj.entityType.FullName} 不可迁移,可迁移属性0个"); + var tbname = _commonUtils.SplitTableName(tb.DbName); + if (tbname?.Length == 1) tbname = new[] { database, tbname[0] }; + + var tboldname = _commonUtils.SplitTableName(tb.DbOldName); //旧表名 + if (tboldname?.Length == 1) tboldname = new[] { database, tboldname[0] }; + if (string.IsNullOrEmpty(obj.tableName) == false) + { + var tbtmpname = _commonUtils.SplitTableName(obj.tableName); + if (tbtmpname?.Length == 1) tbtmpname = new[] { database, tbtmpname[0] }; + if (tbname[0] != tbtmpname[0] || tbname[1] != tbtmpname[1]) + { + tbname = tbtmpname; + tboldname = null; + } + } + + if (string.Compare(tbname[0], database, true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tbname[0])) == null) //创建数据库 + sb.Append($"CREATE DATABASE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(" default charset utf8 COLLATE utf8_general_ci;\r\n"); + + var sbalter = new StringBuilder(); + var istmpatler = false; //创建临时表,导入数据,删除旧表,修改 + if (LocalExecuteScalar(tbname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tbname)) == null) + { //表不存在 + if (tboldname != null) + { + if (string.Compare(tboldname[0], tbname[0], true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tboldname[0])) == null || + LocalExecuteScalar(tboldname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tboldname)) == null) + //数据库或表不存在 + tboldname = null; + } + if (tboldname == null) + { + //创建表 + var createTableName = _commonUtils.QuoteSqlName(tbname[0], tbname[1]); + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(createTableName).Append(" ( "); + foreach (var tbcol in tb.ColumnsByPosition) + { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); + if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)); + sb.Append(","); + } + if (tb.Primarys.Any()) + { + sb.Append(" \r\n PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + //创建表的索引,感谢 @mafeng8,这样写可以支持自增不是主键的情况 + foreach (var uk in tb.Indexes) + { + sb.Append(" \r\n "); + if (uk.IsUnique) sb.Append("UNIQUE "); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append("("); + foreach (var tbcol in uk.Columns) + { + sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sb.Append(" DESC"); + sb.Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); + sb.Append("\r\n) Engine=InnoDB"); + if (string.IsNullOrEmpty(tb.Comment) == false) + sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); + sb.Append(";\r\n"); + continue; + } + //如果新表,旧表在一个数据库下,直接修改表名 + if (string.Compare(tbname[0], tboldname[0], true) == 0) + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tboldname[0], tboldname[1])).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); + else + { + //如果新表,旧表不在一起,创建新表,导入数据,删除旧表 + istmpatler = true; + } + } + else + tboldname = null; //如果新表已经存在,不走改表名逻辑 + + //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 + var sql = _commonUtils.FormatSql(@" +select +a.column_name, +a.column_type, +case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', +case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity', +a.column_comment 'comment' +from information_schema.columns a +where a.table_schema in ({0}) and a.table_name in ({1})", tboldname ?? tbname); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a => + { + var a1 = string.Concat(a[1]); + if (a1 == "datetime") a1 = string.Concat(a1, "(0)"); + return new + { + column = string.Concat(a[0]), + sqlType = a1, + is_nullable = string.Concat(a[2]) == "1", + is_identity = string.Concat(a[3]) == "1", + is_unsigned = string.Concat(a[1]).EndsWith(" unsigned"), + comment = string.Concat(a[4]) + }; + }, StringComparer.CurrentCultureIgnoreCase); + + if (istmpatler == false) + { + var existsPrimary = LocalExecuteScalar(tbname[0], _commonUtils.FormatSql(" select 1 from information_schema.key_column_usage where table_schema={0} and table_name={1} and constraint_name = 'PRIMARY' limit 1", tbname)); + foreach (var tbcol in tb.ColumnsByPosition) + { + if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || + string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol)) + { + var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? ""); + var isDbTypeChanged = tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false; + if (tbstructcol.sqlType == "datetime(0)" && Regex.IsMatch(tbcol.Attribute.DbType, @"datetime\s*\(", RegexOptions.IgnoreCase) == false) + isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase) == false; + else if (tbstructcol.sqlType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase) == false || + (int.TryParse(Regex.Match(tbcol.Attribute.DbType, @"datetime\s*\((\d*)", RegexOptions.IgnoreCase).Groups[1].Value, out var trydtrd) ? trydtrd : 3) != + (int.TryParse(Regex.Match(tbstructcol.sqlType, @"datetime\s*\((\d*)", RegexOptions.IgnoreCase).Groups[1].Value, out var trydtrd2) ? trydtrd2 : 3); + else if (tbstructcol.sqlType.StartsWith("int", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("int", StringComparison.CurrentCultureIgnoreCase) == false; + else if (tbstructcol.sqlType.StartsWith("tinyint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("tinyint", StringComparison.CurrentCultureIgnoreCase) == false; + else if (tbstructcol.sqlType.StartsWith("smallint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("smallint", StringComparison.CurrentCultureIgnoreCase) == false; + else if (tbstructcol.sqlType.StartsWith("bigint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("bigint", StringComparison.CurrentCultureIgnoreCase) == false; + + if ((tbcol.Attribute.DbType.IndexOf(" unsigned", StringComparison.CurrentCultureIgnoreCase) != -1) != tbstructcol.is_unsigned || + isDbTypeChanged || + tbcol.Attribute.IsNullable != tbstructcol.is_nullable || + tbcol.Attribute.IsIdentity != tbstructcol.is_identity || + isCommentChanged) + { + if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable && tbcol.Attribute.IsNullable == false && tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) + sbalter.Append("UPDATE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" = ").Append(tbcol.DbDefaultValue).Append(" WHERE ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" IS NULL;\r\n"); + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(tbcol.Attribute.DbType); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); + if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); + if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); + sbalter.Append(";\r\n"); + } + if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0) + { + //修改列名 + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" CHANGE COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); + if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); + if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); + sbalter.Append(";\r\n"); + } + continue; + } + //添加列 + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" ADD ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); + if (tbcol.Attribute.IsNullable == false && tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) sbalter.Append(" DEFAULT ").Append(tbcol.DbDefaultValue); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); + if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); + if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); + sbalter.Append(";\r\n"); + } + var dsuksql = _commonUtils.FormatSql(@" +select +a.column_name, +a.index_name 'index_id', +0 'IsDesc', +case when a.non_unique = 0 then 1 else 0 end 'IsUnique' +from information_schema.statistics a +where a.table_schema IN ({0}) and a.table_name IN ({1}) and a.index_name <> 'PRIMARY'", tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]), string.Concat(a[2]), string.Concat(a[3]) }); + foreach (var uk in tb.Indexes) + { + if (string.IsNullOrEmpty(uk.Name) || uk.Columns.Any() == false) continue; + var ukname = ReplaceIndexName(uk.Name, tbname[1]); + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], ukname, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Columns.Length || dsukfind1.Where(a => (a[3] == "1") == uk.IsUnique && uk.Columns.Where(b => string.Compare(b.Column.Attribute.Name, a[0], true) == 0 && (a[2] == "1") == b.IsDesc).Any()).Count() != uk.Columns.Length) + { + if (dsukfind1.Any()) sbalter.Append("DROP INDEX ").Append(_commonUtils.QuoteSqlName(ukname)).Append(" ON ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); + sbalter.Append("CREATE "); + if (uk.IsUnique) sbalter.Append("UNIQUE "); + sbalter.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ukname)).Append(" ON ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append("("); + foreach (var tbcol in uk.Columns) + { + sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sbalter.Append(" DESC"); + sbalter.Append(", "); + } + sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); + } + } + } + if (istmpatler == false) + { + var dbcomment = string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(@" select table_comment from information_schema.tables where table_schema = {0} and table_name = {1}", tbname[0], tbname[1]))); + if (dbcomment != (tb.Comment ?? "")) + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" COMMENT ").Append(" ").Append(_commonUtils.FormatSql("{0}", tb.Comment ?? "")).Append(";\r\n"); + + sb.Append(sbalter); + continue; + } + + //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名 + var tablename = tboldname == null ? _commonUtils.QuoteSqlName(tbname[0], tbname[1]) : _commonUtils.QuoteSqlName(tboldname[0], tboldname[1]); + var tmptablename = _commonUtils.QuoteSqlName(tbname[0], $"FreeSqlTmp_{tbname[1]}"); + //创建临时表 + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(tmptablename).Append(" ( "); + foreach (var tbcol in tb.ColumnsByPosition) + { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); + if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)); + sb.Append(","); + } + if (tb.Primarys.Any()) + { + sb.Append(" \r\n PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); + sb.Append("\r\n) Engine=InnoDB"); + if (string.IsNullOrEmpty(tb.Comment) == false) + sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); + sb.Append(";\r\n"); + + sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); + foreach (var tbcol in tb.ColumnsByPosition) + sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append(")\r\nSELECT "); + foreach (var tbcol in tb.ColumnsByPosition) + { + var insertvalue = "NULL"; + if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || + string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol)) + { + insertvalue = _commonUtils.QuoteSqlName(tbstructcol.column); + if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false) + { + //insertvalue = $"cast({insertvalue} as {tbcol.Attribute.DbType.Split(' ').First()})"; + } + if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable) + insertvalue = $"ifnull({insertvalue},{tbcol.DbDefaultValue})"; + } + else if (tbcol.Attribute.IsNullable == false) + if (tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) + insertvalue = tbcol.DbDefaultValue; + sb.Append(insertvalue).Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(";\r\n"); + sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n"); + sb.Append("ALTER TABLE ").Append(tmptablename).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); + //创建表的索引 + foreach (var uk in tb.Indexes) + { + sb.Append("CREATE "); + if (uk.IsUnique) sb.Append("UNIQUE "); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append(" ON ").Append(tablename).Append("("); + foreach (var tbcol in uk.Columns) + { + sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sb.Append(" DESC"); + sb.Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append(");\r\n"); + } + } + return sb.Length == 0 ? null : sb.ToString(); + } + finally + { + try + { + if (string.IsNullOrEmpty(database) == false) + conn.Value.ChangeDatabase(database); + _orm.Ado.MasterPool.Return(conn); + } + catch + { + _orm.Ado.MasterPool.Return(conn, true); + } + } + + object LocalExecuteScalar(string db, string sql) + { + if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(db); + try + { + using (var cmd = conn.Value.CreateCommand()) + { + cmd.CommandText = sql; + cmd.CommandType = CommandType.Text; + return cmd.ExecuteScalar(); + } + } + finally + { + if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(database); + } + } + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs new file mode 100644 index 00000000..c3b490b0 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs @@ -0,0 +1,445 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ClickHouse.Client.ADO; + +namespace FreeSql.ClickHouse +{ + class ClickHouseDbFirst : IDbFirst + { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public ClickHouseDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public int GetDbType(DbColumnInfo column) => (int)GetClickHouseDbType(column); + DbType GetClickHouseDbType(DbColumnInfo column) + { + var is_unsigned = column.DbTypeTextFull.ToLower().EndsWith(" unsigned"); + switch (column.DbTypeText.ToLower()) + { + case "bit": + case "tinyint": + case "bool": + case "sbyte": + case "Int8": return DbType.SByte; + case "byte": + case "UInt8": return DbType.Byte; + case "smallint": + case "Int16": return DbType.Int16; + case "UInt16": return DbType.UInt16; + case "Int32": + case "int": return DbType.Int32; + case "uint": + case "UInt32": return DbType.UInt32; + case "bigint": + case "Int64": + case "long": return DbType.Int64; + case "UInt64": + case "ulong": return DbType.UInt64; + case "real": + case "Float64": + case "double": return DbType.Double; + case "Float32": + case "float": return DbType.Single; + case "date": return DbType.Date; + case "datetime": return DbType.DateTime; + case "datetime64": return DbType.DateTime; + case "tinytext": + case "text": + case "mediumtext": + case "longtext": + case "char": + case "varchar": return DbType.String; + default: return DbType.String; + } + } + + static readonly Dictionary _dicDbToCs = new Dictionary() { + { (int)DbType.SByte, new DbToCs("(sbyte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetByte") }, + { (int)DbType.Int16, new DbToCs("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") }, + { (int)DbType.Int32, new DbToCs("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)DbType.Int64, new DbToCs("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") }, + + { (int)DbType.Byte, new DbToCs("(byte?)", "byte.Parse({0})", "{0}.ToString()", "byte?", typeof(byte), typeof(byte?), "{0}.Value", "GetByte") }, + { (int)DbType.UInt16, new DbToCs("(ushort?)", "ushort.Parse({0})", "{0}.ToString()", "ushort?", typeof(ushort), typeof(ushort?), "{0}.Value", "GetInt16") }, + { (int)DbType.UInt32, new DbToCs("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt32") }, + { (int)DbType.UInt64, new DbToCs("(ulong?)", "ulong.Parse({0})", "{0}.ToString()", "ulong?", typeof(ulong), typeof(ulong?), "{0}.Value", "GetInt64") }, + + { (int)DbType.Double, new DbToCs("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, + { (int)DbType.Single, new DbToCs("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + + { (int)DbType.Date, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)DbType.Date, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)DbType.DateTime, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)DbType.DateTime2, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + + { (int)DbType.Guid, new DbToCs("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid?", typeof(Guid), typeof(Guid?), "{0}", "GetString") }, + { (int)DbType.String, new DbToCs("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + }; + + public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null; + public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null; + public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + + public List GetDatabases() + { + var sql = @" select name from system.databases where name not in ('system', 'default')"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList(); + } + + public bool ExistsTable(string name, bool ignoreCase) + { + if (string.IsNullOrEmpty(name)) return false; + var tbname = _commonUtils.SplitTableName(name); + if (tbname?.Length == 1) + { + var database = ""; + using (var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5))) + { + database = conn.Value.Database; + } + tbname = new[] { database, tbname[0] }; + } + if (ignoreCase) tbname = tbname.Select(a => a.ToLower()).ToArray(); + var sql = $" SELECT 1 FROM information_schema.TABLES WHERE {(ignoreCase ? "lower(table_schema)" : "table_schema")} = {_commonUtils.FormatSql("{0}", tbname[0])} and {(ignoreCase ? "lower(table_name)" : "table_name")} = {_commonUtils.FormatSql("{0}", tbname[1])}"; + return string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, sql)) == "1"; + } + + public DbTableInfo GetTableByName(string name, bool ignoreCase = true) => GetTables(null, name, ignoreCase)?.FirstOrDefault(); + public List GetTablesByDatabase(params string[] database) => GetTables(database, null, false); + + public List GetTables(string[] database, string tablename, bool ignoreCase) + { + var loc1 = new List(); + var loc2 = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var loc3 = new Dictionary>(StringComparer.CurrentCultureIgnoreCase); + string[] tbname = null; + if (string.IsNullOrEmpty(tablename) == false) + { + tbname = _commonUtils.SplitTableName(tablename); + if (tbname?.Length == 1) + { + using (var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5))) + { + if (string.IsNullOrEmpty(conn.Value.Database)) return loc1; + tbname = new[] { conn.Value.Database, tbname[0] }; + } + } + if (ignoreCase) tbname = tbname.Select(a => a.ToLower()).ToArray(); + database = new[] { tbname[0] }; + } + else if (database == null || database.Any() == false) + { + using (var conn = _orm.Ado.MasterPool.Get()) + { + if (string.IsNullOrEmpty(conn.Value.Database)) return loc1; + database = new[] { conn.Value.Database }; + } + } + + var databaseIn = string.Join(",", database.Select(a => _commonUtils.FormatSql("{0}", a))); + var sql = $@" +select +concat(a.table_schema, '.', a.table_name) 'id', +a.table_schema 'schema', +a.table_name 'table', +a.table_comment, +a.table_type 'type' +from information_schema.tables a +where {(ignoreCase ? "lower(a.table_schema)" : "a.table_schema")} in ({databaseIn}){(tbname == null ? "" : $" and {(ignoreCase ? "lower(a.table_name)" : "a.table_name")}={_commonUtils.FormatSql("{0}", tbname[1])}")}"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var loc6 = new List(); + var loc66 = new List(); + var loc6_1000 = new List(); + var loc66_1000 = new List(); + foreach (var row in ds) + { + var table_id = string.Concat(row[0]); + var schema = string.Concat(row[1]); + var table = string.Concat(row[2]); + var comment = string.Concat(row[3]); + var type = string.Concat(row[4]) == "VIEW" ? DbTableType.VIEW : DbTableType.TABLE; + if (database.Length == 1) + { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + schema = ""; + } + loc2.Add(table_id, new DbTableInfo { Id = table_id, Schema = schema, Name = table, Comment = comment, Type = type }); + loc3.Add(table_id, new Dictionary(StringComparer.CurrentCultureIgnoreCase)); + switch (type) + { + case DbTableType.TABLE: + case DbTableType.VIEW: + loc6_1000.Add(table.Replace("'", "''")); + if (loc6_1000.Count >= 500) + { + loc6.Add(loc6_1000.ToArray()); + loc6_1000.Clear(); + } + break; + case DbTableType.StoreProcedure: + loc66_1000.Add(table.Replace("'", "''")); + if (loc66_1000.Count >= 500) + { + loc66.Add(loc66_1000.ToArray()); + loc66_1000.Clear(); + } + break; + } + } + if (loc6_1000.Count > 0) loc6.Add(loc6_1000.ToArray()); + if (loc66_1000.Count > 0) loc66.Add(loc66_1000.ToArray()); + + if (loc6.Count == 0) return loc1; + var loc8 = new StringBuilder().Append("("); + for (var loc8idx = 0; loc8idx < loc6.Count; loc8idx++) + { + if (loc8idx > 0) loc8.Append(" OR "); + loc8.Append("a.table_name in ("); + for (var loc8idx2 = 0; loc8idx2 < loc6[loc8idx].Length; loc8idx2++) + { + if (loc8idx2 > 0) loc8.Append(","); + loc8.Append($"'{loc6[loc8idx][loc8idx2]}'"); + } + loc8.Append(")"); + } + loc8.Append(")"); + + sql = $@" +select +concat(a.table_schema, '.', a.table_name), +a.column_name, +a.data_type, +ifnull(a.character_maximum_length, 0) 'len', +a.column_type, +case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', +case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity', +a.column_comment 'comment', +a.column_default 'default_value' +from information_schema.columns a +where {(ignoreCase ? "lower(a.table_schema)" : "a.table_schema")} in ({databaseIn}) and {loc8} +"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var position = 0; + foreach (var row in ds) + { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string type = string.Concat(row[2]); + //long max_length = long.Parse(string.Concat(row[3])); + string sqlType = string.Concat(row[4]); + var m_len = Regex.Match(sqlType, @"\w+\((\d+)"); + int max_length = m_len.Success ? int.Parse(m_len.Groups[1].Value) : -1; + bool is_nullable = string.Concat(row[5]) == "1"; + bool is_identity = string.Concat(row[6]) == "1"; + string comment = string.Concat(row[7]); + string defaultValue = string.Concat(row[8]); + if (max_length == 0) max_length = -1; + if (database.Length == 1) + { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + } + loc3[table_id].Add(column, new DbColumnInfo + { + Name = column, + MaxLength = max_length, + IsIdentity = is_identity, + IsNullable = is_nullable, + IsPrimary = false, + DbTypeText = type, + DbTypeTextFull = sqlType, + Table = loc2[table_id], + Coment = comment, + DefaultValue = defaultValue, + Position = ++position + }); + loc3[table_id][column].DbType = this.GetDbType(loc3[table_id][column]); + loc3[table_id][column].CsType = this.GetCsTypeInfo(loc3[table_id][column]); + } + + sql = $@" +select +concat(a.table_schema, '.', a.table_name) 'table_id', +a.column_name, +a.index_name 'index_id', +case when a.non_unique = 0 then 1 else 0 end 'IsUnique', +case when a.index_name = 'PRIMARY' then 1 else 0 end 'IsPrimaryKey', +0 'IsClustered', +0 'IsDesc' +from information_schema.statistics a +where {(ignoreCase ? "lower(a.table_schema)" : "a.table_schema")} in ({databaseIn}) and {loc8} +"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var indexColumns = new Dictionary>(StringComparer.CurrentCultureIgnoreCase); + var uniqueColumns = new Dictionary>(StringComparer.CurrentCultureIgnoreCase); + foreach (var row in ds) + { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string index_id = string.Concat(row[2]); + bool is_unique = string.Concat(row[3]) == "1"; + bool is_primary_key = string.Concat(row[4]) == "1"; + bool is_clustered = string.Concat(row[5]) == "1"; + bool is_desc = string.Concat(row[6]) == "1"; + if (database.Length == 1) + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key; + + Dictionary loc10 = null; + DbIndexInfo loc11 = null; + if (!indexColumns.TryGetValue(table_id, out loc10)) + indexColumns.Add(table_id, loc10 = new Dictionary(StringComparer.CurrentCultureIgnoreCase)); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new DbIndexInfo()); + loc11.Columns.Add(new DbIndexColumnInfo { Column = loc9, IsDesc = is_desc }); + if (is_unique && !is_primary_key) + { + if (!uniqueColumns.TryGetValue(table_id, out loc10)) + uniqueColumns.Add(table_id, loc10 = new Dictionary(StringComparer.CurrentCultureIgnoreCase)); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new DbIndexInfo()); + loc11.Columns.Add(new DbIndexColumnInfo { Column = loc9, IsDesc = is_desc }); + } + } + foreach (string table_id in indexColumns.Keys) + { + foreach (var column in indexColumns[table_id]) + loc2[table_id].IndexesDict.Add(column.Key, column.Value); + } + foreach (string table_id in uniqueColumns.Keys) + { + foreach (var column in uniqueColumns[table_id]) + { + column.Value.Columns.Sort((c1, c2) => c1.Column.Name.CompareTo(c2.Column.Name)); + loc2[table_id].UniquesDict.Add(column.Key, column.Value); + } + } + + if (tbname == null) + { + sql = $@" +select +concat(a.constraint_schema, '.', a.table_name) 'table_id', +a.column_name, +a.constraint_name 'FKId', +concat(a.referenced_table_schema, '.', a.referenced_table_name) 'ref_table_id', +1 'IsForeignKey', +a.referenced_column_name 'ref_column' +from information_schema.key_column_usage a +where {(ignoreCase ? "lower(a.constraint_schema)" : "a.constraint_schema")} in ({databaseIn}) and {loc8} and not isnull(position_in_unique_constraint) +"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var fkColumns = new Dictionary>(StringComparer.CurrentCultureIgnoreCase); + foreach (var row in ds) + { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string fk_id = string.Concat(row[2]); + string ref_table_id = string.Concat(row[3]); + bool is_foreign_key = string.Concat(row[4]) == "1"; + string referenced_column = string.Concat(row[5]); + if (database.Length == 1) + { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + ref_table_id = ref_table_id.Substring(ref_table_id.IndexOf('.') + 1); + } + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc2.ContainsKey(ref_table_id) == false) continue; + var loc10 = loc2[ref_table_id]; + var loc11 = loc3[ref_table_id][referenced_column]; + + Dictionary loc12 = null; + DbForeignInfo loc13 = null; + if (!fkColumns.TryGetValue(table_id, out loc12)) + fkColumns.Add(table_id, loc12 = new Dictionary(StringComparer.CurrentCultureIgnoreCase)); + if (!loc12.TryGetValue(fk_id, out loc13)) + loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc10 }); + loc13.Columns.Add(loc9); + loc13.ReferencedColumns.Add(loc11); + } + foreach (var table_id in fkColumns.Keys) + foreach (var fk in fkColumns[table_id]) + loc2[table_id].ForeignsDict.Add(fk.Key, fk.Value); + } + + foreach (var table_id in loc3.Keys) + { + foreach (var loc5 in loc3[table_id].Values) + { + loc2[table_id].Columns.Add(loc5); + if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5); + if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5); + } + } + foreach (var loc4 in loc2.Values) + { + //if (loc4.Primarys.Count == 0 && loc4.UniquesDict.Count > 0) + //{ + // foreach (var loc5 in loc4.UniquesDict.First().Value.Columns) + // { + // loc5.Column.IsPrimary = true; + // loc4.Primarys.Add(loc5.Column); + // } + //} + loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc4.Columns.Sort((c1, c2) => + { + int compare = c2.IsPrimary.CompareTo(c1.IsPrimary); + if (compare == 0) + { + bool b1 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c1.Name).Any()).Any(); + bool b2 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c2.Name).Any()).Any(); + compare = b2.CompareTo(b1); + } + if (compare == 0) compare = c1.Name.CompareTo(c2.Name); + return compare; + }); + loc1.Add(loc4); + } + loc1.Sort((t1, t2) => + { + var ret = t1.Schema.CompareTo(t2.Schema); + if (ret == 0) ret = t1.Name.CompareTo(t2.Name); + return ret; + }); + + loc2.Clear(); + loc3.Clear(); + return loc1; + } + + public List GetEnumsByDatabase(params string[] database) + { + return new List(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs new file mode 100644 index 00000000..072de4ee --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs @@ -0,0 +1,559 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.ClickHouse +{ + class ClickHouseExpression : CommonExpression + { + + public ClickHouseExpression(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; + if (arrOper.Type == typeof(byte[])) return $"length({getExp(arrOper)})"; + break; + case ExpressionType.Convert: + var operandExp = (exp as UnaryExpression)?.Operand; + var gentype = exp.Type.NullableTypeOrThis(); + if (gentype != operandExp.Type.NullableTypeOrThis()) + { + switch (gentype.ToString()) + { + case "System.Boolean": return $"({getExp(operandExp)} not in ('0','false'))"; + case "System.Byte": return $"cast({getExp(operandExp)} as Int8)"; + case "System.Char": return $"substr(cast({getExp(operandExp)} as String), 1, 1)"; + case "System.DateTime": return $"cast({getExp(operandExp)} as DateTime)"; + case "System.Decimal": return $"cast({getExp(operandExp)} as Float64)"; + case "System.Double": return $"cast({getExp(operandExp)} as Float64)"; + case "System.Int16": return $"cast({getExp(operandExp)} as Int16)"; + case "System.Int32": return $"cast({getExp(operandExp)} as Int32)"; + case "System.Int64": return $"cast({getExp(operandExp)} as Int64)"; + case "System.SByte": return $"cast({getExp(operandExp)} as UInt8)"; + case "System.Single": return $"cast({getExp(operandExp)} as Float32)"; + case "System.String": return $"cast({getExp(operandExp)} as String)"; + case "System.UInt16": return $"cast({getExp(operandExp)} as UInt16)"; + case "System.UInt32": return $"cast({getExp(operandExp)} as UInt32)"; + case "System.UInt64": return $"cast({getExp(operandExp)} as UInt64)"; + case "System.Guid": return $"substr(cast({getExp(operandExp)} as String), 1, 36)"; + } + } + 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 Int8)"; + case "System.Char": return $"substr(cast({getExp(callExp.Arguments[0])} as String), 1, 1)"; + case "System.DateTime": return $"cast({getExp(callExp.Arguments[0])} as DateTime)"; + case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as Float64)"; + case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as Float64)"; + case "System.Int16": return $"cast({getExp(callExp.Arguments[0])} as Int16)"; + case "System.Int32": return $"cast({getExp(callExp.Arguments[0])} as Int32)"; + case "System.Int64": return $"cast({getExp(callExp.Arguments[0])} as Int64)"; + case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as UInt8)"; + case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as Float32)"; + case "System.UInt16": return $"cast({getExp(callExp.Arguments[0])} as UInt16)"; + case "System.UInt32": return $"cast({getExp(callExp.Arguments[0])} as UInt32)"; + case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} as UInt64)"; + case "System.Guid": return $"substr(cast({getExp(callExp.Arguments[0])} as String), 1, 36)"; + } + return null; + case "NewGuid": + return null; + case "Next": + if (callExp.Object?.Type == typeof(Random)) return "cast(rand()*1000000000 as Int64)"; + return null; + case "NextDouble": + if (callExp.Object?.Type == typeof(Random)) return "rand()"; + return null; + case "Random": + if (callExp.Method.DeclaringType.IsNumberType()) return "rand()"; + return null; + case "ToString": + if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"cast({getExp(callExp.Object)} as String)" : 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 $"substr({getExp(callExp.Arguments[0])}, 1, 1)"; + } + } + } + if (objType == null) objType = callExp.Method.DeclaringType; + if (objType != null || objType.IsArrayOrList()) + { + if (argIndex >= callExp.Arguments.Count) break; + tsc.SetMapColumnTmp(null); + var args1 = getExp(callExp.Arguments[argIndex]); + var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + var oldDbParams = tsc.SetDbParamsReturnOld(null); + var left = objExp == null ? null : getExp(objExp); + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + tsc.SetDbParamsReturnOld(oldDbParams); + switch (callExp.Method.Name) + { + case "Contains": + //判断 in //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 + return $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; + } + } + 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(","); + if (a % 500 == 499) arrSb.Append(" \r\n \r\n"); //500元素分割, 3空格\r\n4空格 + 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 $"char_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 "curdate()"; + case "MinValue": return "cast('0001/1/1 0:00:00' as DateTime)"; + case "MaxValue": return "cast('9999/12/31 23:59:59' as DateTime)"; + } + return null; + } + var left = ExpressionLambdaToSql(exp.Expression, tsc); + + switch (exp.Member.Name) + { + case "Date": return $"toDate({left})"; + case "TimeOfDay": return $"dateDiff(second, toDate({left}), toDateTime({left}))*1000000"; + case "DayOfWeek": return $"(toDayOfWeek({left})-1)"; + case "Day": return $"toDayOfMonth({left})"; + case "DayOfYear": return $"toDayOfYear({left})"; + case "Month": return $"toMonth({left})"; + case "Year": return $"toYear({left})"; + case "Hour": return $"toHour({left})"; + case "Minute": return $"toMinute({left})"; + case "Second": return $"toSecond({left})"; + case "Millisecond": return $"(toSecond({left})/1000)"; + case "Ticks": return $"(dateDiff(second, toDate('0001-1-1'), toDateTime({left}))*10000000+621355968000000000)"; + } + return null; + } + public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Zero": return "0"; + case "MinValue": return "-922337203685477580"; //微秒 Ticks / 10 + case "MaxValue": return "922337203685477580"; + } + return null; + } + var left = ExpressionLambdaToSql(exp.Expression, tsc); + switch (exp.Member.Name) + { + case "Days": return $"intDiv(({left})/{60 * 60 * 24})"; + case "Hours": return $"intDiv(({left})/{60 * 60}%24)"; + case "Milliseconds": return $"(cast({left} as Int64)*1000)"; + case "Minutes": return $"intDiv(({left})/60%60)"; + case "Seconds": return $"(({left})%60)"; + case "Ticks": return $"(intDiv({left} as Int64)*10000000)"; + case "TotalDays": return $"(({left})/{60 * 60 * 24})"; + case "TotalHours": return $"(({left})/{60 * 60})"; + case "TotalMilliseconds": return $"(cast({left} as Int64)*1000)"; + case "TotalMinutes": return $"(({left})/60)"; + case "TotalSeconds": return $"({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": + return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), null); + case "Format": + if (exp.Arguments[0].NodeType != ExpressionType.Constant) throw new Exception($"未实现函数表达式 {exp} 解析,参数 {exp.Arguments[0]} 必须为常量"); + if (exp.Arguments.Count == 1) return ExpressionLambdaToSql(exp.Arguments[0], tsc); + 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 => $"',{_common.IsNull(ExpressionLambdaToSql(a, tsc), "''")},'").ToArray(); + return $"concat({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("StringJoinClickHouseGroupConcat"), + 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 args0Value = getExp(exp.Arguments[0]); + if (args0Value == "NULL") return $"({left}) IS NULL"; + if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"concat({args0Value}, '%')")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"concat('%', {args0Value})")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) LIKE concat('%', {args0Value}, '%')"; + 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 $"substr({left}, {substrArgs1})"; + return $"substr({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 $"(locate({indexOfFindStr}, {left}, {locateArgs1})-1)"; + } + return $"(locate({indexOfFindStr}, {left})-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})"; + } + 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) + { + if (exp.Method.Name == "Trim") left = $"trim({getExp(argsTrim01)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(leading {getExp(argsTrim01)} from {left})"; + if (exp.Method.Name == "TrimEnd") left = $"trim(trailing {getExp(argsTrim01)} from {left})"; + } + } + return left; + case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "CompareTo": return $"strcmp({left}, {getExp(exp.Arguments[0])})"; + 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])})"; + 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 $"pow({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 $"truncate({getExp(exp.Arguments[0])}, 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 $"({getExp(exp.Arguments[0])} - ({getExp(exp.Arguments[1])}))"; + case "DaysInMonth": return $"toDayOfMonth(subtractDays(addMonths(toStartOfMonth(concat({getExp(exp.Arguments[0])}, '-', {getExp(exp.Arguments[1])}, '-01')), 1), 1))"; + 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 $"cast({getExp(exp.Arguments[0])} as DateTime)"; + case "ParseExact": + case "TryParse": + case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as DateTime)"; + } + } + else + { + var left = getExp(exp.Object); + var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); + switch (exp.Method.Name) + { + case "Add": return $"addSeconds(toDateTime({left}), {args1})"; + case "AddDays": return $"addDays(toDateTime({left}), {args1})"; + case "AddHours": return $"addHours(toDateTime({left}), {args1})"; + case "AddMilliseconds": return $"addSeconds(toDateTime({left}), {args1}/1000)"; + case "AddMinutes": return $"addMinutes(toDateTime({left}),{args1})"; + case "AddMonths": return $"addMonths(toDateTime({left}),{args1})"; + case "AddSeconds": return $"addSeconds(toDateTime({left}),{args1})"; + case "AddTicks": return $"addSeconds(toDateTime({left}), {args1}/10000000)"; + case "AddYears": return $"addYears(toDateTime({left}),{args1})"; + case "Subtract": + switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GetGenericArguments().FirstOrDefault() : exp.Arguments[0].Type).FullName) + { + case "System.DateTime": return $"dateDiff(second, {args1}, toDateTime({left}))"; + case "System.TimeSpan": return $"addSeconds(toDateTime({left}),(({args1})*-1))"; + } + break; + case "Equals": return $"({left} = {args1})"; + case "CompareTo": return $"dateDiff(second,{args1},toDateTime({left}))"; + case "ToString": + if (exp.Arguments.Count == 0) return $"date_format({left},'%Y-%m-%d %H:%M:%S.%f')"; + switch (args1) + { + case "'yyyy-MM-dd HH:mm:ss'": return $"formatDateTime(toDateTime({left}),'%Y-%m-%d %H:%M:%S')"; + case "'yyyy-MM-dd HH:mm'": return $"formatDateTime(toDateTime({left}),'%Y-%m-%d %H:%M')"; + case "'yyyy-MM-dd HH'": return $"formatDateTime(toDateTime({left}),'%Y-%m-%d %H')"; + case "'yyyy-MM-dd'": return $"formatDateTime(toDateTime({left}),'%Y-%m-%d')"; + case "'yyyy-MM'": return $"formatDateTime(toDateTime({left}),'%Y-%m')"; + case "'yyyyMMddHHmmss'": return $"formatDateTime(toDateTime({left}),'%Y%m%d%H%M%S')"; + case "'yyyyMMddHHmm'": return $"formatDateTime(toDateTime({left}),'%Y%m%d%H%M')"; + case "'yyyyMMddHH'": return $"formatDateTime(toDateTime({left}),'%Y%m%d%H')"; + case "'yyyyMMdd'": return $"formatDateTime(toDateTime({left}),'%Y%m%d')"; + case "'yyyyMM'": return $"formatDateTime(toDateTime({left}),'%Y%m')"; + case "'yyyy'": return $"formatDateTime(toDateTime({left}),'%Y')"; + case "'HH:mm:ss'": return $"formatDateTime(toDateTime({left}),'%H:%M:%S')"; + } + args1 = Regex.Replace(args1, "(yyyy|yy|MM|M|dd|d|HH|H|hh|h|mm|ss|tt)", m => + { + switch (m.Groups[1].Value) + { + case "yyyy": return $"%Y"; + case "yy": return $"%y"; + case "MM": return $"%_a1"; + case "M": return $"%c"; + case "dd": return $"%d"; + case "d": return $"%e"; + case "HH": return $"%H"; + case "H": return $"%k"; + case "hh": return $"%h"; + case "h": return $"%l"; + case "mm": return $"%i"; + case "ss": return $"%_a2"; + case "tt": return $"%p"; + } + return m.Groups[0].Value; + }); + var argsFinds = new[] { "%Y", "%y", "%_a1", "%c", "%d", "%e", "%H", "%k", "%h", "%l", "%M", "%_a2", "%p" }; + var argsSpts = Regex.Split(args1, "(m|s|t)"); + for (var a = 0; a < argsSpts.Length; a++) + { + switch (argsSpts[a]) + { + case "m": argsSpts[a] = $"case when substr(formatDateTime(toDateTime({left}),'%M'),1,1) = '0' then substr(formatDateTime(toDateTime({left}),'%M'),2,1) else formatDateTime(toDateTime({left}),'%M') end"; break; + case "s": argsSpts[a] = $"case when substr(formatDateTime(toDateTime({left}),'%S'),1,1) = '0' then substr(formatDateTime(toDateTime({left}),'%S'),2,1) else formatDateTime(toDateTime({left}),'%S') end"; break; + case "t": argsSpts[a] = $"trim(trailing 'M' from formatDateTime(toDateTime({left}),'%p'))"; 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)) ? $"formatDateTime(toDateTime({left}),'{argsSptsA}')" : $"'{argsSptsA}'"; + break; + } + } + if (argsSpts.Length > 0) args1 = $"concat({string.Join(", ", argsSpts.Where(a => a != "''"))})"; + return args1.Replace("%_a1", "%m").Replace("%_a2", "%S"); + } + } + return null; + } + public override string ExpressionLambdaToSqlCallTimeSpan(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + case "Compare": return $"({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])}))"; + case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})"; + case "FromDays": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60 * 24})"; + case "FromHours": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60})"; + case "FromMilliseconds": return $"(({getExp(exp.Arguments[0])})*1000)"; + case "FromMinutes": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60})"; + case "FromSeconds": return $"(({getExp(exp.Arguments[0])})*1000000)"; + case "FromTicks": return $"(({getExp(exp.Arguments[0])})/10)"; + case "Parse": return $"cast({getExp(exp.Arguments[0])} as Int64)"; + case "ParseExact": + case "TryParse": + case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as Int64)"; + } + } + else + { + var left = getExp(exp.Object); + var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); + switch (exp.Method.Name) + { + case "Add": return $"({left}+{args1})"; + case "Subtract": return $"({left}-({args1}))"; + case "Equals": return $"({left} = {args1})"; + case "CompareTo": return $"({left}-({args1}))"; + case "ToString": return $"cast({left} as String)"; + } + } + 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 Int8)"; + case "ToChar": return $"substr(cast({getExp(exp.Arguments[0])} as String), 1, 1)"; + case "ToDateTime": return $"cast({getExp(exp.Arguments[0])} as DateTime)"; + case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as Float64)"; + case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as Float64)"; + case "ToInt16": + case "ToInt32": + case "ToInt64": + case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as UInt8)"; + case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as Float32)"; + case "ToString": return $"cast({getExp(exp.Arguments[0])} as String)"; + case "ToUInt16": + case "ToUInt32": + case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as UInt64)"; + } + } + return null; + } + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs new file mode 100644 index 00000000..d0f362a5 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs @@ -0,0 +1,18 @@ +using FreeSql; +using FreeSql.ClickHouse.Curd; +using System; + +public static partial class FreeSqlClickHouseGlobalExtensions +{ + + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatClickHouse(this string that, params object[] args) => _clickHouseAdo.Addslashes(that, args); + static FreeSql.ClickHouse.ClickHouseAdo _clickHouseAdo = new FreeSql.ClickHouse.ClickHouseAdo(); + + +} diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseProvider.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseProvider.cs new file mode 100644 index 00000000..41ff2dba --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseProvider.cs @@ -0,0 +1,42 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.ClickHouse.Curd; +using System; +using System.Data.Common; +using System.Linq.Expressions; +using System.Threading; + +namespace FreeSql.ClickHouse +{ + + public class ClickHouseProvider : BaseDbProvider, IFreeSql + { + public override ISelect CreateSelectProvider(object dywhere) => new ClickHouseSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public override IInsert CreateInsertProvider() => new ClickHouseInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + public override IUpdate CreateUpdateProvider(object dywhere) => new ClickHouseUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public override IDelete CreateDeleteProvider(object dywhere) => new ClickHouseDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public override IInsertOrUpdate CreateInsertOrUpdateProvider() + { + throw new NotImplementedException(); + } + public ClickHouseProvider(string masterConnectionString, string[] slaveConnectionString, Func connectionFactory = null) + { + this.InternalCommonUtils = new ClickHouseUtils(this); + this.InternalCommonExpression = new ClickHouseExpression(this.InternalCommonUtils); + + this.Ado = new ClickHouseAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString, connectionFactory); + this.Aop = new AopProvider(); + + this.DbFirst = new ClickHouseDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.CodeFirst = new ClickHouseCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + } + + ~ClickHouseProvider() => this.Dispose(); + int _disposeCounter; + public override void Dispose() + { + if (Interlocked.Increment(ref _disposeCounter) != 1) return; + (this.Ado as AdoProvider)?.Dispose(); + } + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs new file mode 100644 index 00000000..9cedcfac --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs @@ -0,0 +1,133 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using ClickHouse.Client.ADO; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Globalization; +using System.Data; +using ClickHouse.Client.ADO.Parameters; + +namespace FreeSql.ClickHouse +{ + + class ClickHouseUtils : CommonUtils + { + public ClickHouseUtils(IFreeSql orm) : base(orm) + { + } + + public override DbParameter AppendParamter(List _params, string parameterName, ColumnInfo col, Type type, object value) + { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + var dbtype = (DbType)_orm.CodeFirst.GetDbInfo(type)?.type; + DbParameter ret = new ClickHouseDbParameter { ParameterName = parameterName, DbType= dbtype, Value = value };//QuoteParamterName(parameterName) + if (col != null) + { + var dbtype2 = (DbType)_orm.DbFirst.GetDbType(new DatabaseModel.DbColumnInfo { DbTypeText = col.DbTypeText, DbTypeTextFull = col.Attribute.DbType, MaxLength = col.DbSize }); + switch (dbtype2) + { + case DbType.Binary: + default: + dbtype = dbtype2; + //if (col.DbSize != 0) ret.Size = col.DbSize; + if (col.DbPrecision != 0) ret.Precision = col.DbPrecision; + if (col.DbScale != 0) ret.Scale = col.DbScale; + break; + } + } + + ret.DbType = dbtype; + _params?.Add(ret); + return ret; + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, "?", (name, type, value) => + { + DbParameter ret = new ClickHouseDbParameter { ParameterName = $"?{name}", Value = value }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) + { + + ret.DbType = (DbType)tp.Value; + } + return ret; + }); + public override string RewriteColumn(ColumnInfo col, string sql) + { + if (string.IsNullOrWhiteSpace(col?.Attribute.RewriteSql) == false) + return string.Format(col.Attribute.RewriteSql, sql); + return string.Format(sql, col.CsType.Name); + } + public override string FormatSql(string sql, params object[] args) => sql?.FormatClickHouse(args); + public override string QuoteSqlName(params string[] name) + { + if (name.Length == 1) + { + var nametrim = name[0].Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + if (nametrim.StartsWith("`") && nametrim.EndsWith("`")) + return nametrim; + return $"`{nametrim.Replace(".", "`.`")}`"; + } + return $"`{string.Join("`.`", name)}`"; + } + public override string TrimQuoteSqlName(string name) + { + var nametrim = name.Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + return $"{nametrim.Trim('`').Replace("`.`", ".").Replace(".`", ".")}"; + } + public override string[] SplitTableName(string name) => GetSplitTableNames(name, '`', '`', 2); + public override string QuoteParamterName(string name) => $"{{{{{name}:{{0}}}}}}"; + public override string IsNull(string sql, object value) => $"ifnull({sql}, {value})"; + public override string StringConcat(string[] objs, Type[] types) => $"concat({string.Join(", ", objs)})"; + public override string Mod(string left, string right, Type leftType, Type rightType) => $"{left} % {right}"; + public override string Div(string left, string right, Type leftType, Type rightType) => $"{left} div {right}"; + public override string Now => "now()"; + public override string NowUtc => "now('UTC')"; + + public override string QuoteWriteParamterAdapter(Type type, string paramterName) + { + switch (type.FullName) + { + case "MygisPoint": + case "MygisLineString": + case "MygisPolygon": + case "MygisMultiPoint": + case "MygisMultiLineString": + case "MygisMultiPolygon": return $"ST_GeomFromText({paramterName})"; + } + return paramterName; + } + protected override string QuoteReadColumnAdapter(Type type, Type mapType, string columnName) + { + switch (mapType.FullName) + { + case "MygisPoint": + case "MygisLineString": + case "MygisPolygon": + case "MygisMultiPoint": + case "MygisMultiLineString": + case "MygisMultiPolygon": return $"ST_AsText({columnName})"; + } + return columnName; + } + + 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); + } + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseDelete.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseDelete.cs new file mode 100644 index 00000000..c950f12b --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseDelete.cs @@ -0,0 +1,35 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.ClickHouse.Curd +{ + + class ClickHouseDelete : Internal.CommonProvider.DeleteProvider + { + public ClickHouseDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override List ExecuteDeleted() + { + throw new NotImplementedException("FreeSql.ClickHouse.Curd 暂未实现"); + } + public override string ToSql() + { + return base.ToSql().Replace("DELETE FROM", "ALTER TABLE").Replace("WHERE", "DELETE WHERE"); + } +#if net40 +#else + public override Task> ExecuteDeletedAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("FreeSql.ClickHouse.Curd 暂未实现"); + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs new file mode 100644 index 00000000..6ddbb87b --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -0,0 +1,178 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.ClickHouse.Curd +{ + + class ClickHouseInsert : Internal.CommonProvider.InsertProvider where T1 : class + { + public ClickHouseInsert(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() + { + if (InternalIsIgnoreInto == false) return base.ToSqlValuesOrSelectUnionAll(); + var sql = base.ToSqlValuesOrSelectUnionAll(); + return $"INSERT IGNORE INTO {sql.Substring(12)}"; + } + + 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); + + async protected override 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; + } + async protected override 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 + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs new file mode 100644 index 00000000..17429a08 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs @@ -0,0 +1,210 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.ClickHouse.Curd +{ + + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select1Provider + { + + 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, 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++) + { + sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(_aliasRule?.Invoke(tbsfrom[a].Table.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++) + { + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).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)) sb.Append(" ON 1 = 1"); + else + { + var onSql = tbsfrom[b].NavigateCondition ?? tbsfrom[b].On; + sb.Append(" ON ").Append(onSql); + if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) + { + if (string.IsNullOrEmpty(onSql)) sb.Append(tbsfrom[b].Cascade); + else sb.Append(" AND ").Append(tbsfrom[b].Cascade); + } + } + } + break; + } + else + { + 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) + { + if (tb.Type == SelectTableInfoType.Parent) continue; + switch (tb.Type) + { + 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(tb.On ?? tb.NavigateCondition); + if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND ").Append(tb.Cascade); + if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")"); + } + if (_join.Length > 0) sb.Append(_join); + + 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 ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override ISelect From(Expression, T2, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect : FreeSql.Internal.CommonProvider.Select2Provider where T2 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select3Provider where T2 : class where T3 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select4Provider where T2 : class where T3 : class where T4 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select5Provider where T2 : class where T3 : class where T4 : class where T5 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select6Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select7Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class + { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } + class MySqlSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs new file mode 100644 index 00000000..c537b065 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -0,0 +1,79 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.ClickHouse.Curd +{ + + class ClickHouseUpdate : Internal.CommonProvider.UpdateProvider + { + + public ClickHouseUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + internal StringBuilder InternalSbSet => _set; + internal StringBuilder InternalSbSetIncr => _setIncr; + internal Dictionary InternalIgnore => _ignore; + public override string ToSql() + { + return base.ToSql().Replace("UPDATE", "ALTER TABLE").Replace("SET", "UPDATE"); + } + internal void InternalResetSource(List source) => _source = source; + internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); + internal void InternalToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) => ToSqlCaseWhenEnd(sb, col); + + public override int ExecuteAffrows() => base.SplitExecuteAffrows(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override List ExecuteUpdated() => base.SplitExecuteUpdated(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + protected override List RawExecuteUpdated() => throw new NotImplementedException("FreeSql.ClickHouse.Custom 未实现该功能 未实现该功能"); + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) + { + if (primarys.Length == 1) + { + var pk = primarys.First(); + caseWhen.Append(_commonUtils.RereadColumn(pk, _commonUtils.QuoteSqlName(pk.Attribute.Name))); + return; + } + caseWhen.Append("CONCAT("); + var pkidx = 0; + foreach (var pk in primarys) + { + if (pkidx > 0) caseWhen.Append(", '+', "); + caseWhen.Append(_commonUtils.RereadColumn(pk, _commonUtils.QuoteSqlName(pk.Attribute.Name))); + ++pkidx; + } + caseWhen.Append(")"); + } + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) + { + if (primarys.Length == 1) + { + sb.Append(_commonUtils.FormatSql("{0}", primarys[0].GetDbValue(d))); + return; + } + sb.Append("concat("); + var pkidx = 0; + foreach (var pk in primarys) + { + if (pkidx > 0) sb.Append(", '+', "); + sb.Append(_commonUtils.FormatSql("{0}", pk.GetDbValue(d))); + ++pkidx; + } + sb.Append(")"); + } + +#if net40 +#else + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task> ExecuteUpdatedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteUpdatedAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + protected override Task> RawExecuteUpdatedAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException("FreeSql.ClickHouse.Custom 未实现该功能 未实现该功能"); +#endif + } +} diff --git a/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj new file mode 100644 index 00000000..83ff3325 --- /dev/null +++ b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj @@ -0,0 +1,44 @@ + + + + netstandard2.1 + 2.6.100 + true + FreeSql;ncc;YeXiangQin + FreeSql 数据库自定义适配,访问所有数据库 + https://github.com/2881099/FreeSql + https://github.com/2881099/FreeSql + git + MIT + FreeSql;ORM + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + False + key.snk + false + + + + + + + + + + + + + 4.0.1.462 + + + + + + + + + + From c22d8d74d256ec35bfc41183d9eabea4ed4353e7 Mon Sep 17 00:00:00 2001 From: chenbo <1114927184@qq.com> Date: Sat, 27 Nov 2021 13:46:41 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9ClickHouse=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClickHouse/ClickHouseTest1.cs | 121 +++++++++- FreeSql.Tests/FreeSql.Tests/g.cs | 2 +- FreeSql/Internal/UtilsExpressionTree.cs | 5 +- .../ClickHouseAdo/ClickHouseAdo.cs | 4 +- .../ClickHouseCodeFirst.cs | 227 +++++++----------- .../ClickHouseDbFirst.cs | 60 +++-- .../ClickHouseExpression.cs | 18 +- .../ClickHouseUtils.cs | 7 +- .../Curd/ClickHouseSelect.cs | 90 +++---- .../Curd/ClickHouseUpdate.cs | 2 +- 10 files changed, 318 insertions(+), 218 deletions(-) diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs index 439b60c7..2f616a07 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -11,13 +11,31 @@ namespace FreeSql.Tests.MySql class TestAuditValue { - public Guid id { get; set; } + [FreeSql.DataAnnotations.Column(IsPrimary = true)] + public long Id { get; set; } [Now] - public DateTime createtime { get; set; } + public DateTime CreateTime { get; set; } + + [FreeSql.DataAnnotations.Column(IsNullable = true )] + public string Name { get; set; } + + [FreeSql.DataAnnotations.Column(IsNullable = false)] + public int Age { get; set; } + + public bool State { get; set; } + + [FreeSql.DataAnnotations.Column(IsNullable = true)] + public bool Enable { get; set; } + + public DateTime? UpdateTime { get; set; } + + public int? Points { get; set; } } [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] public class TestClickHouse { + [FreeSql.DataAnnotations.Column(IsPrimary = true)] + [Now] public long Id { get; set; } public string Name { get; set; } @@ -27,21 +45,29 @@ namespace FreeSql.Tests.MySql [Fact] public void AuditValue() { - var date = DateTime.Now.Date; - var item = new TestAuditValue(); - + var id = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); + var item = new TestClickHouse(); + item.Id = id; + item.Name = "李四"; EventHandler audit = (s, e) => - { - if (e.Property.GetCustomAttribute(false) != null) - e.Value = DateTime.Now.Date; - }; - g.mysql.Aop.AuditValue += audit; + { + if (e.Property.GetCustomAttribute(false) != null) + e.Value = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); + }; + g.clickHouse.Aop.AuditValue += audit; - g.mysql.Insert(item).ExecuteAffrows(); + g.clickHouse.Insert(item).ExecuteAffrows(); - g.mysql.Aop.AuditValue -= audit; + g.clickHouse.Aop.AuditValue -= audit; - Assert.Equal(item.createtime, date); + Assert.Equal(item.Id, id); + } + + + [Fact] + public void CreateTalbe() + { + g.clickHouse.CodeFirst.SyncStructure(); } [Fact] @@ -61,6 +87,7 @@ namespace FreeSql.Tests.MySql var items = fsql.Select().Where(o=>o.Id>900).OrderByDescending(o=>o.Id).ToList(); Assert.Equal(100, items.Count); } + [Fact] public void TestPage() { @@ -72,6 +99,7 @@ namespace FreeSql.Tests.MySql .Count(out var count).ToList(); Assert.Equal(100, list.Count); } + [Fact] public void TestDelete() { @@ -81,6 +109,7 @@ namespace FreeSql.Tests.MySql var count2 = fsql.Select().Count(); Assert.NotEqual(count1, count2); } + [Fact] public void TestUpdate() { @@ -91,5 +120,71 @@ namespace FreeSql.Tests.MySql } + [Fact] + public void TestRepositorySelect() + { + var fsql = g.clickHouse; + var list=fsql.GetRepository().Where(o => o.Id > 900) + .ToList(); + + } + + [Fact] + public void TestRepositoryInsert() + { + var fsql = g.clickHouse; + long id = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(),0); + var list=fsql.GetRepository().Insert(new TestClickHouse { Id= id, Name="张三"}); + var data=fsql.GetRepository().Get(id); + } + + [Fact] + public void TestDateTime() + { + var fsql = g.clickHouse; + long id = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); + DateTime createTime=DateTime.Now; + fsql.Insert(new TestAuditValue { + Id = id, CreateTime = createTime, Age =18,Name="张三" + }).ExecuteAffrows(); + + var date1 = fsql.GetRepository().Where(o => o.CreateTime == createTime) + .ToList(); + var date2 = fsql.GetRepository().Where(o => o.CreateTime.Date == createTime.Date) + .ToList(); + var date3 = fsql.GetRepository().Where(o => o.CreateTime.Year == createTime.Year) + .ToList(); + var date4 = fsql.GetRepository().Where(o => o.CreateTime.Month == createTime.Month) + .ToList(); + var date5 = fsql.GetRepository().Where(o => o.CreateTime.Second == createTime.Second) + .ToList(); + var date6 = fsql.GetRepository().Where(o => o.CreateTime.Millisecond == createTime.Millisecond) + .ToList(); + var date7 = fsql.GetRepository().Where(o => o.CreateTime.AddSeconds(10) < createTime) + .ToList(); + + } + + [Fact] + public void TestRepositoryUpdateTime() + { + //暂时无法修改 + var fsql = g.clickHouse; + var repository=fsql.GetRepository(); + var list = repository.Select.ToList(); + list.ForEach(o=>o.UpdateTime = DateTime.Now); + repository.Update(list); + + } + + [Fact] + public void TestUpdateTime() + { + var fsql = g.clickHouse; + var state=fsql.GetRepository().UpdateDiy.Set(o=>o.UpdateTime,DateTime.Now).Where(o=>1==1).ExecuteAffrows(); + + + } + } } diff --git a/FreeSql.Tests/FreeSql.Tests/g.cs b/FreeSql.Tests/FreeSql.Tests/g.cs index 8fca5b2a..df205522 100644 --- a/FreeSql.Tests/FreeSql.Tests/g.cs +++ b/FreeSql.Tests/FreeSql.Tests/g.cs @@ -14,7 +14,7 @@ public class g //.UseAutoSyncStructure(true) //.UseGenerateCommandParameterWithLambda(true) .UseMonitorCommand( - cmd => Trace.WriteLine("\r\n线程" + Thread.CurrentThread.ManagedThreadId + ": " + cmd.CommandText) //监听SQL命令对象,在执行前 + cmd => Debug.WriteLine("\r\n线程" + Thread.CurrentThread.ManagedThreadId + ": " + cmd.CommandText) //监听SQL命令对象,在执行前 //, (cmd, traceLog) => Console.WriteLine(traceLog) ) .UseLazyLoading(true) diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index ffe5d765..9db2a0c3 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -118,7 +118,7 @@ namespace FreeSql.Internal var leftBt = colattr.DbType.IndexOf('('); colattr.DbType = colattr.DbType.Substring(0, leftBt).ToUpper() + colattr.DbType.Substring(leftBt); } - else + else if(common._orm.Ado.DataType != DataType.ClickHouse) colattr.DbType = colattr.DbType.ToUpper(); if (colattrIsNull == false && colattrIsNullable == true) colattr.DbType = colattr.DbType.Replace("NOT NULL", ""); @@ -129,12 +129,13 @@ namespace FreeSql.Internal if (common.CodeFirst.IsSyncStructureToLower) colattr.Name = colattr.Name.ToLower(); if (common.CodeFirst.IsSyncStructureToUpper) colattr.Name = colattr.Name.ToUpper(); - if ((colattr.IsNullable != true || colattr.IsIdentity == true || colattr.IsPrimary == true) && colattr.DbType.Contains("NOT NULL") == false) + if ((colattr.IsNullable != true || colattr.IsIdentity == true || colattr.IsPrimary == true) && colattr.DbType.Contains("NOT NULL") == false && common._orm.Ado.DataType != DataType.ClickHouse) { colattr.IsNullable = false; colattr.DbType = Regex.Replace(colattr.DbType, @"\bNULL\b", "").Trim() + " NOT NULL"; } if (colattr.IsNullable == true && colattr.DbType.Contains("NOT NULL")) colattr.DbType = colattr.DbType.Replace("NOT NULL", ""); + else if (colattr.IsNullable == true && !colattr.DbType.Contains("Nullable") && common._orm.Ado.DataType == DataType.ClickHouse)colattr.DbType = $"Nullable({colattr.DbType})" ; colattr.DbType = Regex.Replace(colattr.DbType, @"\([^\)]+\)", m => { var tmpLt = Regex.Replace(m.Groups[0].Value, @"\s", ""); diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs index cbd805ef..0eb76626 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs @@ -14,7 +14,7 @@ namespace FreeSql.ClickHouse { public ClickHouseAdo() : base(DataType.ClickHouse, null, null) { } - public ClickHouseAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, Func connectionFactory) : base(DataType.MySql, masterConnectionString, slaveConnectionStrings) + public ClickHouseAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, Func connectionFactory) : base(DataType.ClickHouse, masterConnectionString, slaveConnectionStrings) { base._util = util; if (connectionFactory != null) @@ -50,7 +50,7 @@ namespace FreeSql.ClickHouse else if (decimal.TryParse(string.Concat(param), out var trydec)) return param; else if (param is DateTime || param is DateTime?) - return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.fff"), "'"); + return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss"), "'"); else if (param is TimeSpan || param is TimeSpan?) return ((TimeSpan)param).Ticks / 10; else if (param is byte[]) diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs index 09b7003e..da491174 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs @@ -84,17 +84,17 @@ namespace FreeSql.ClickHouse } } - if (string.Compare(tbname[0], database, true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tbname[0])) == null) //创建数据库 - sb.Append($"CREATE DATABASE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(" default charset utf8 COLLATE utf8_general_ci;\r\n"); + if (string.Compare(tbname[0], database, true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from system.databases d where name={0}", tbname[0])) == null) //创建数据库 + sb.Append($"CREATE DATABASE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(" ENGINE=Ordinary;\r\n"); var sbalter = new StringBuilder(); var istmpatler = false; //创建临时表,导入数据,删除旧表,修改 - if (LocalExecuteScalar(tbname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tbname)) == null) + if (LocalExecuteScalar(tbname[0], _commonUtils.FormatSql(" SELECT 1 FROM system.tables t WHERE database ={0} and name ={1}", tbname)) == null) { //表不存在 if (tboldname != null) { - if (string.Compare(tboldname[0], tbname[0], true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tboldname[0])) == null || - LocalExecuteScalar(tboldname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tboldname)) == null) + if (string.Compare(tboldname[0], tbname[0], true) != 0 && LocalExecuteScalar(database, _commonUtils.FormatSql(" select 1 from system.databases where name={0}", tboldname[0])) == null || + LocalExecuteScalar(tboldname[0], _commonUtils.FormatSql(" SELECT 1 FROM system.tables WHERE database={0} and name={1}", tboldname)) == null) //数据库或表不存在 tboldname = null; } @@ -105,36 +105,43 @@ namespace FreeSql.ClickHouse sb.Append("CREATE TABLE IF NOT EXISTS ").Append(createTableName).Append(" ( "); foreach (var tbcol in tb.ColumnsByPosition) { + tbcol.Attribute.DbType = tbcol.Attribute.DbType.Replace(" NOT NULL", ""); sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); - if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)); sb.Append(","); } - if (tb.Primarys.Any()) - { - sb.Append(" \r\n PRIMARY KEY ("); - foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append("),"); - } - //创建表的索引,感谢 @mafeng8,这样写可以支持自增不是主键的情况 + foreach (var uk in tb.Indexes) { sb.Append(" \r\n "); - if (uk.IsUnique) sb.Append("UNIQUE "); sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append("("); foreach (var tbcol in uk.Columns) { sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); - if (tbcol.IsDesc) sb.Append(" DESC"); - sb.Append(", "); + sb.Append("TYPE set(8192) GRANULARITY 5, "); } sb.Remove(sb.Length - 2, 2).Append("),"); } sb.Remove(sb.Length - 1, 1); - sb.Append("\r\n) Engine=InnoDB"); - if (string.IsNullOrEmpty(tb.Comment) == false) - sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); - sb.Append(";\r\n"); + sb.Append("\r\n) "); + sb.Append("\r\nENGINE = MergeTree()"); + + if (tb.Primarys.Any()) + { + sb.Append(" \r\nORDER BY ( "); + var ls = new StringBuilder(); + foreach (var tbcol in tb.Primarys) ls.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Append(ls); + sb.Remove(sb.Length - 2, 2); + sb.Append(" )"); + sb.Append(" \r\nPRIMARY KEY "); + sb.Append(ls); + sb.Remove(sb.Length - 2, 2).Append(","); + } + sb.Remove(sb.Length - 1, 1); + //if (string.IsNullOrEmpty(tb.Comment) == false) + // sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); + sb.Append(" SETTINGS index_granularity = 8192;\r\n"); continue; } //如果新表,旧表在一个数据库下,直接修改表名 @@ -152,122 +159,57 @@ namespace FreeSql.ClickHouse //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 var sql = _commonUtils.FormatSql(@" select -a.column_name, -a.column_type, -case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', -case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity', -a.column_comment 'comment' -from information_schema.columns a -where a.table_schema in ({0}) and a.table_name in ({1})", tboldname ?? tbname); +a.name, +a.type, +if(ilike(a.`type`, 'Nullable(%S%)'),'is_nullable','0'), +a.comment as comment, +a.is_in_partition_key, +a.is_in_sorting_key, +a.is_in_primary_key, +a.is_in_sampling_key +from system.columns a +where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a => { - var a1 = string.Concat(a[1]); - if (a1 == "datetime") a1 = string.Concat(a1, "(0)"); return new { column = string.Concat(a[0]), - sqlType = a1, + sqlType = (string)a[1], is_nullable = string.Concat(a[2]) == "1", - is_identity = string.Concat(a[3]) == "1", - is_unsigned = string.Concat(a[1]).EndsWith(" unsigned"), - comment = string.Concat(a[4]) + is_identity = false, + comment = string.Concat(a[3]), + is_primary= string.Concat(a[6]) == "1", }; }, StringComparer.CurrentCultureIgnoreCase); if (istmpatler == false) { - var existsPrimary = LocalExecuteScalar(tbname[0], _commonUtils.FormatSql(" select 1 from information_schema.key_column_usage where table_schema={0} and table_name={1} and constraint_name = 'PRIMARY' limit 1", tbname)); + var existsPrimary = tbstruct.Any(o => o.Value.is_primary); foreach (var tbcol in tb.ColumnsByPosition) { if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol)) { var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? ""); - var isDbTypeChanged = tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false; - if (tbstructcol.sqlType == "datetime(0)" && Regex.IsMatch(tbcol.Attribute.DbType, @"datetime\s*\(", RegexOptions.IgnoreCase) == false) - isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase) == false; - else if (tbstructcol.sqlType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("datetime", StringComparison.CurrentCultureIgnoreCase) == false || - (int.TryParse(Regex.Match(tbcol.Attribute.DbType, @"datetime\s*\((\d*)", RegexOptions.IgnoreCase).Groups[1].Value, out var trydtrd) ? trydtrd : 3) != - (int.TryParse(Regex.Match(tbstructcol.sqlType, @"datetime\s*\((\d*)", RegexOptions.IgnoreCase).Groups[1].Value, out var trydtrd2) ? trydtrd2 : 3); - else if (tbstructcol.sqlType.StartsWith("int", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("int", StringComparison.CurrentCultureIgnoreCase) == false; - else if (tbstructcol.sqlType.StartsWith("tinyint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("tinyint", StringComparison.CurrentCultureIgnoreCase) == false; - else if (tbstructcol.sqlType.StartsWith("smallint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("smallint", StringComparison.CurrentCultureIgnoreCase) == false; - else if (tbstructcol.sqlType.StartsWith("bigint", StringComparison.CurrentCultureIgnoreCase)) isDbTypeChanged = tbcol.Attribute.DbType.StartsWith("bigint", StringComparison.CurrentCultureIgnoreCase) == false; - - if ((tbcol.Attribute.DbType.IndexOf(" unsigned", StringComparison.CurrentCultureIgnoreCase) != -1) != tbstructcol.is_unsigned || - isDbTypeChanged || - tbcol.Attribute.IsNullable != tbstructcol.is_nullable || - tbcol.Attribute.IsIdentity != tbstructcol.is_identity || - isCommentChanged) - { - if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable && tbcol.Attribute.IsNullable == false && tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) - sbalter.Append("UPDATE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" = ").Append(tbcol.DbDefaultValue).Append(" WHERE ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" IS NULL;\r\n"); - sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(tbcol.Attribute.DbType); - if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); - if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); - if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); - sbalter.Append(";\r\n"); - } + if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false || + tbcol.Attribute.IsNullable != tbstructcol.is_nullable || isCommentChanged) + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(tbcol.Attribute.IsNullable ? $"Nullable({tbcol.Attribute.DbType.Split(' ').First()})":tbcol.Attribute.DbType.Split(' ').First()).Append(";\r\n"); + if(isCommentChanged) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" COMMENT COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")).Append(";\r\n"); if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0) - { - //修改列名 - sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" CHANGE COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); - if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); - if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); - if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); - sbalter.Append(";\r\n"); - } + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" TO ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(";\r\n"); continue; } //添加列 sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" ADD ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); if (tbcol.Attribute.IsNullable == false && tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) sbalter.Append(" DEFAULT ").Append(tbcol.DbDefaultValue); if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "")); - if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" AUTO_INCREMENT"); - if (tbcol.Attribute.IsIdentity == true) sbalter.Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); sbalter.Append(";\r\n"); } - var dsuksql = _commonUtils.FormatSql(@" -select -a.column_name, -a.index_name 'index_id', -0 'IsDesc', -case when a.non_unique = 0 then 1 else 0 end 'IsUnique' -from information_schema.statistics a -where a.table_schema IN ({0}) and a.table_name IN ({1}) and a.index_name <> 'PRIMARY'", tboldname ?? tbname); - var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]), string.Concat(a[2]), string.Concat(a[3]) }); - foreach (var uk in tb.Indexes) - { - if (string.IsNullOrEmpty(uk.Name) || uk.Columns.Any() == false) continue; - var ukname = ReplaceIndexName(uk.Name, tbname[1]); - var dsukfind1 = dsuk.Where(a => string.Compare(a[1], ukname, true) == 0).ToArray(); - if (dsukfind1.Any() == false || dsukfind1.Length != uk.Columns.Length || dsukfind1.Where(a => (a[3] == "1") == uk.IsUnique && uk.Columns.Where(b => string.Compare(b.Column.Attribute.Name, a[0], true) == 0 && (a[2] == "1") == b.IsDesc).Any()).Count() != uk.Columns.Length) - { - if (dsukfind1.Any()) sbalter.Append("DROP INDEX ").Append(_commonUtils.QuoteSqlName(ukname)).Append(" ON ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); - sbalter.Append("CREATE "); - if (uk.IsUnique) sbalter.Append("UNIQUE "); - sbalter.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ukname)).Append(" ON ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append("("); - foreach (var tbcol in uk.Columns) - { - sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); - if (tbcol.IsDesc) sbalter.Append(" DESC"); - sbalter.Append(", "); - } - sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); - } - } - } - if (istmpatler == false) - { - var dbcomment = string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(@" select table_comment from information_schema.tables where table_schema = {0} and table_name = {1}", tbname[0], tbname[1]))); - if (dbcomment != (tb.Comment ?? "")) - sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(" COMMENT ").Append(" ").Append(_commonUtils.FormatSql("{0}", tb.Comment ?? "")).Append(";\r\n"); - sb.Append(sbalter); - continue; } + //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名 var tablename = tboldname == null ? _commonUtils.QuoteSqlName(tbname[0], tbname[1]) : _commonUtils.QuoteSqlName(tboldname[0], tboldname[1]); var tmptablename = _commonUtils.QuoteSqlName(tbname[0], $"FreeSqlTmp_{tbname[1]}"); @@ -275,27 +217,45 @@ where a.table_schema IN ({0}) and a.table_name IN ({1}) and a.index_name <> 'PRI sb.Append("CREATE TABLE IF NOT EXISTS ").Append(tmptablename).Append(" ( "); foreach (var tbcol in tb.ColumnsByPosition) { + tbcol.Attribute.DbType = tbcol.Attribute.DbType.Replace(" NOT NULL", ""); sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType); - if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)); sb.Append(","); } - if (tb.Primarys.Any()) + + foreach (var uk in tb.Indexes) { - sb.Append(" \r\n PRIMARY KEY ("); - foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Append(" \r\n "); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append("("); + foreach (var tbcol in uk.Columns) + { + sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + sb.Append("TYPE set(8192) GRANULARITY 5, "); + } sb.Remove(sb.Length - 2, 2).Append("),"); } sb.Remove(sb.Length - 1, 1); - sb.Append("\r\n) Engine=InnoDB"); - if (string.IsNullOrEmpty(tb.Comment) == false) - sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); - sb.Append(";\r\n"); + sb.Append("\r\n) "); + sb.Append("\r\nENGINE = MergeTree()"); - sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); - foreach (var tbcol in tb.ColumnsByPosition) - sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")\r\nSELECT "); + if (tb.Primarys.Any()) + { + sb.Append(" \r\nORDER BY ( "); + var ls = new StringBuilder(); + foreach (var tbcol in tb.Primarys) ls.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Append(ls); + sb.Remove(sb.Length - 2, 2); + sb.Append(" )"); + sb.Append(" \r\nPRIMARY KEY "); + sb.Append(ls); + sb.Remove(sb.Length - 2, 2).Append(","); + } + sb.Remove(sb.Length - 1, 1); + //if (string.IsNullOrEmpty(tb.Comment) == false) + // sb.Append(" Comment=").Append(_commonUtils.FormatSql("{0}", tb.Comment)); + sb.Append(" SETTINGS index_granularity = 8192;\r\n"); + + sb.Append("INSERT INTO ").Append(tmptablename).Append(" SELECT "); foreach (var tbcol in tb.ColumnsByPosition) { var insertvalue = "NULL"; @@ -311,27 +271,13 @@ where a.table_schema IN ({0}) and a.table_name IN ({1}) and a.index_name <> 'PRI insertvalue = $"ifnull({insertvalue},{tbcol.DbDefaultValue})"; } else if (tbcol.Attribute.IsNullable == false) - if (tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) + if (tbcol.DbDefaultValue != "NULL") insertvalue = tbcol.DbDefaultValue; sb.Append(insertvalue).Append(", "); } sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(";\r\n"); sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n"); - sb.Append("ALTER TABLE ").Append(tmptablename).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); - //创建表的索引 - foreach (var uk in tb.Indexes) - { - sb.Append("CREATE "); - if (uk.IsUnique) sb.Append("UNIQUE "); - sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append(" ON ").Append(tablename).Append("("); - foreach (var tbcol in uk.Columns) - { - sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); - if (tbcol.IsDesc) sb.Append(" DESC"); - sb.Append(", "); - } - sb.Remove(sb.Length - 2, 2).Append(");\r\n"); - } + sb.Append("RENAME TABLE ").Append(tmptablename).Append(" TO ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])).Append(";\r\n"); } return sb.Length == 0 ? null : sb.ToString(); } @@ -367,5 +313,18 @@ where a.table_schema IN ({0}) and a.table_name IN ({1}) and a.index_name <> 'PRI } } } + + public override int ExecuteDDLStatements(string ddl) + { + if (string.IsNullOrEmpty(ddl)) return 0; + var scripts = ddl.Split(new string[] { ";\r\n" }, StringSplitOptions.None).Where(a => string.IsNullOrEmpty(a.Trim()) == false).ToArray(); + + if (scripts.Any() == false) return 0; + + var affrows = 0; + foreach (var script in scripts) + affrows += base.ExecuteDDLStatements(script); + return affrows; + } } } \ No newline at end of file diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs index c3b490b0..c0c6da19 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs @@ -26,43 +26,67 @@ namespace FreeSql.ClickHouse public int GetDbType(DbColumnInfo column) => (int)GetClickHouseDbType(column); DbType GetClickHouseDbType(DbColumnInfo column) { - var is_unsigned = column.DbTypeTextFull.ToLower().EndsWith(" unsigned"); + if (column.DbTypeText == "Nullable") + { + column.DbTypeText = column.DbTypeTextFull; + //(?<=\()(\S +)(?=\)) + } switch (column.DbTypeText.ToLower()) { case "bit": case "tinyint": case "bool": case "sbyte": - case "Int8": return DbType.SByte; + case "int8": + case "nullable(int8)": return DbType.SByte; case "byte": - case "UInt8": return DbType.Byte; + case "uint8": + case "nullable(uint8)": return DbType.Byte; case "smallint": - case "Int16": return DbType.Int16; - case "UInt16": return DbType.UInt16; - case "Int32": - case "int": return DbType.Int32; + case "int16": + case "nullable(int16)": return DbType.Int16; + case "uint16": + case "nullable(uint16)": return DbType.UInt16; + case "int32": + case "int": + case "nullable(int32)": return DbType.Int32; case "uint": - case "UInt32": return DbType.UInt32; + case "uint32": + case "nullable(uint32)": return DbType.UInt32; case "bigint": - case "Int64": - case "long": return DbType.Int64; - case "UInt64": - case "ulong": return DbType.UInt64; + case "int64": + case "long": + case "nullable(int64)": return DbType.Int64; + case "uint64": + case "ulong": + case "nullable(uint64)": return DbType.UInt64; case "real": case "Float64": - case "double": return DbType.Double; + case "double": + case "nullable(float64)": return DbType.Double; case "Float32": - case "float": return DbType.Single; - case "date": return DbType.Date; - case "datetime": return DbType.DateTime; - case "datetime64": return DbType.DateTime; + case "float": + case "nullable(float32)": return DbType.Single; + case "date": + case "nullable(date)": return DbType.Date; + case "datetime": + case "nullable(datetime)": return DbType.DateTime; + case "datetime64": + case "nullable(datetime64)": return DbType.DateTime; case "tinytext": case "text": case "mediumtext": case "longtext": case "char": + case "string": + case "nullable(string)": case "varchar": return DbType.String; - default: return DbType.String; + default: + { + if (column.DbTypeText.ToLower().Contains("datetime")) + return DbType.DateTime; + return DbType.String; + } } } diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs index 072de4ee..ba173483 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs @@ -201,7 +201,10 @@ namespace FreeSql.ClickHouse return null; } var left = ExpressionLambdaToSql(exp.Expression, tsc); - + if ((exp.Expression as MemberExpression)?.Expression.NodeType == ExpressionType.Constant) + left = $"toDateTime({left})"; + + //IsDate(left); switch (exp.Member.Name) { case "Date": return $"toDate({left})"; @@ -219,6 +222,19 @@ namespace FreeSql.ClickHouse } return null; } + public bool IsInt(string _string) + { + if (string.IsNullOrEmpty(_string)) + return false; + int i = 0; + return int.TryParse(_string, out i); + } + public bool IsDate(string date) + { + if (string.IsNullOrEmpty(date)) + return true; + return DateTime.TryParse(date, out var time); + } public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc) { if (exp.Expression == null) diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs index 9cedcfac..9bb6d484 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs @@ -35,6 +35,10 @@ namespace FreeSql.ClickHouse if (col.DbScale != 0) ret.Scale = col.DbScale; break; } + if (value is bool) + { + ret.Value = (bool)value ? 1 : 0; + } } ret.DbType = dbtype; @@ -56,9 +60,10 @@ namespace FreeSql.ClickHouse }); public override string RewriteColumn(ColumnInfo col, string sql) { + col.Attribute.DbType = col.Attribute.DbType.Replace(" NOT NULL", ""); if (string.IsNullOrWhiteSpace(col?.Attribute.RewriteSql) == false) return string.Format(col.Attribute.RewriteSql, sql); - return string.Format(sql, col.CsType.Name); + return string.Format(sql, col.Attribute.DbType); } public override string FormatSql(string sql, params object[] args) => sql?.FormatClickHouse(args); public override string QuoteSqlName(params string[] name) diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs index 17429a08..0f018e6a 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseSelect.cs @@ -113,98 +113,98 @@ namespace FreeSql.ClickHouse.Curd } public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } - public override ISelect From(Expression, T2, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } - public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } - public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } - public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 ClickHouseSelect(_orm, _commonUtils, _commonExpression, null); ClickHouseSelect.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 MySqlSelect : FreeSql.Internal.CommonProvider.Select2Provider where T2 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select2Provider where T2 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : FreeSql.Internal.CommonProvider.Select3Provider where T2 : class where T3 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select3Provider where T2 : class where T3 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : FreeSql.Internal.CommonProvider.Select4Provider where T2 : class where T3 : class where T4 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select4Provider where T2 : class where T3 : class where T4 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : FreeSql.Internal.CommonProvider.Select5Provider where T2 : class where T3 : class where T4 : class where T5 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select5Provider where T2 : class where T3 : class where T4 : class where T5 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : FreeSql.Internal.CommonProvider.Select6Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select6Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : FreeSql.Internal.CommonProvider.Select7Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class + class ClickHouseSelect : FreeSql.Internal.CommonProvider.Select7Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class { - public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } - class MySqlSelect : 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 + class ClickHouseSelect : 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 MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public ClickHouseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } public override string ToSql(string field = null) => ClickHouseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _tosqlAppendContent, _whereGlobalFilter, _orm); } } diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index c537b065..eb4ba3a0 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -24,7 +24,7 @@ namespace FreeSql.ClickHouse.Curd internal Dictionary InternalIgnore => _ignore; public override string ToSql() { - return base.ToSql().Replace("UPDATE", "ALTER TABLE").Replace("SET", "UPDATE"); + return base.ToSql()?.Replace("UPDATE", "ALTER TABLE").Replace("SET", "UPDATE"); } internal void InternalResetSource(List source) => _source = source; internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); From 4c7e04376f21515d2087f72bfd991ff01cc656c9 Mon Sep 17 00:00:00 2001 From: chenbo <1114927184@qq.com> Date: Sat, 27 Nov 2021 21:48:47 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E6=9B=B4=E6=94=B9CaseWhen=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=88=E5=BE=85=E4=BF=AE=E6=94=B9=E5=AD=97=E6=AE=B5=E5=80=BC?= =?UTF-8?q?=E4=B8=BANULL=E6=97=B6=E8=BF=98=E6=98=AF=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs | 2 ++ .../FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs index 2f616a07..55133c34 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -173,6 +173,7 @@ namespace FreeSql.Tests.MySql var repository=fsql.GetRepository(); var list = repository.Select.ToList(); list.ForEach(o=>o.UpdateTime = DateTime.Now); + list.ForEach(o => o.Enable = true); repository.Update(list); } @@ -182,6 +183,7 @@ namespace FreeSql.Tests.MySql { var fsql = g.clickHouse; var state=fsql.GetRepository().UpdateDiy.Set(o=>o.UpdateTime,DateTime.Now).Where(o=>1==1).ExecuteAffrows(); + //var state1 = fsql.GetRepository().UpdateDiy.Set(o => o.UpdateTime, null).Where(o => 1 == 1).ExecuteAffrows(); } diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index eb4ba3a0..a284c98d 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -68,7 +68,10 @@ namespace FreeSql.ClickHouse.Curd } sb.Append(")"); } - + protected override void ToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) + { + sb.Insert(sb.Length - 4, $" ELSE {_commonUtils.QuoteSqlName(col.Attribute.Name)}"); + } #if net40 #else public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); From 846c1801912951ec2bf65b6342bafc103fd46d36 Mon Sep 17 00:00:00 2001 From: ChenBo <1114927184@qq.com> Date: Sun, 28 Nov 2021 21:37:10 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E6=95=B0=E6=8D=AE=E7=9A=84=E6=96=B9=E5=BC=8F?= =?UTF-8?q?(=E6=B5=8B=E8=AF=95100w=E6=9D=A15=E7=A7=92=E4=B8=8D=E5=88=B0)?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=94=B9=E6=95=B0=E6=8D=AE=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E6=9B=BF=E6=8D=A2=E5=8F=AF=E8=83=BD=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=88=B0=E6=95=B0=E6=8D=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.DbContext/FreeSql.DbContext.xml | 9 -- .../ClickHouse/ClickHouseTest1.cs | 21 ++++- .../Curd/ClickHouseInsert.cs | 86 ++++++++++++++++++- .../Curd/ClickHouseUpdate.cs | 6 +- 4 files changed, 104 insertions(+), 18 deletions(-) diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index bdd16ff9..da7ace6b 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -538,14 +538,5 @@ - - - 批量注入 Repository,可以参考代码自行调整 - - - - - - diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs index 55133c34..22cece67 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Reflection; using System.Text; using Xunit; +using System.Linq; +using System.Collections; +using System.Diagnostics; namespace FreeSql.Tests.MySql { @@ -32,13 +35,18 @@ namespace FreeSql.Tests.MySql public int? Points { get; set; } } [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] - public class TestClickHouse + public class TestClickHouse : IEnumerable { [FreeSql.DataAnnotations.Column(IsPrimary = true)] [Now] public long Id { get; set; } public string Name { get; set; } + public IEnumerator GetEnumerator() + { + yield return Id; + yield return Name; + } } class NowAttribute: Attribute { } @@ -73,9 +81,10 @@ namespace FreeSql.Tests.MySql [Fact] public void TestInsert() { + Stopwatch stopwatch =new Stopwatch(); var fsql = g.clickHouse; List list=new List(); - for (int i = 0; i < 1000; i++) + for (int i = 0; i < 1000000; i++) { list.Add(new TestClickHouse() { @@ -83,9 +92,13 @@ namespace FreeSql.Tests.MySql Name = $"测试{i}" }); } + fsql.Delete().Where(t => 1 == 1).ExecuteAffrows(); + stopwatch.Start(); fsql.Insert(list).ExecuteAffrows(); - var items = fsql.Select().Where(o=>o.Id>900).OrderByDescending(o=>o.Id).ToList(); - Assert.Equal(100, items.Count); + stopwatch.Stop(); + Debug.WriteLine(list.Count+"条用时:" +stopwatch.ElapsedMilliseconds.ToString()); + //var items = fsql.Select().Where(o=>o.Id>900).OrderByDescending(o=>o.Id).ToList(); + //Assert.Equal(100, items.Count); } [Fact] diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs index 6ddbb87b..57f606bb 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -1,9 +1,12 @@ -using FreeSql.Internal; +using ClickHouse.Client.ADO; +using ClickHouse.Client.Copy; +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 System.Threading; using System.Threading.Tasks; @@ -30,9 +33,9 @@ namespace FreeSql.ClickHouse.Curd 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 int ExecuteAffrows() => base.SplitExecuteAffrows(int.MaxValue, int.MaxValue); + public override long ExecuteIdentity() => base.SplitExecuteIdentity(int.MaxValue, int.MaxValue); + public override List ExecuteInserted() => base.SplitExecuteInserted(int.MaxValue, int.MaxValue); public override string ToSql() @@ -42,6 +45,81 @@ namespace FreeSql.ClickHouse.Curd return $"INSERT IGNORE INTO {sql.Substring(12)}"; } + protected override int RawExecuteAffrows() + { + var affrows = 0; + Exception exception = null; + Aop.CurdBeforeEventArgs before=null; + if (_source.Count>1) + { + try + { + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, null, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + using var bulkCopyInterface = new ClickHouseBulkCopy(_orm.Ado.MasterPool.Get().Value as ClickHouseConnection) + { + DestinationTableName = _table.DbName, + BatchSize = _source.Count + }; + bulkCopyInterface.WriteToServerAsync(ToDataTable(),default).Wait(); + return affrows; + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, affrows); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + } + else + { + var sql = this.ToSql(); + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + try + { + affrows = _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, affrows); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + return affrows; + } + } + + private IDictionary GetValue(T u, System.Reflection.PropertyInfo[] columns) + { + try + { + Dictionary dic = new Dictionary(); + foreach (var item in columns) + { + object v = null; + if (u != null) + { + v = item.GetValue(u); + } + dic.TryAdd(item.Name, v); + } + return dic; + } + catch (Exception e) + { + throw; + } + } + protected override long RawExecuteIdentity() { var sql = this.ToSql(); diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index a284c98d..16745836 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -24,7 +24,11 @@ namespace FreeSql.ClickHouse.Curd internal Dictionary InternalIgnore => _ignore; public override string ToSql() { - return base.ToSql()?.Replace("UPDATE", "ALTER TABLE").Replace("SET", "UPDATE"); + var sql = base.ToSql(); + sql = sql.Remove(0, 6).Insert(0, "ALTER TABLE"); + var index = sql.IndexOf(" SET "); + sql = sql.Remove(index, 5).Insert(index, " UPDATE "); + return sql; } internal void InternalResetSource(List source) => _source = source; internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); From ae0f2e9d0dfff33c0393babb25ba7c9981427b50 Mon Sep 17 00:00:00 2001 From: chenbo <1114927184@qq.com> Date: Tue, 30 Nov 2021 15:02:21 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E9=87=8D=E5=86=99=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=8E=BB=E9=99=A4=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E5=92=8C=E4=BF=AE=E6=94=B9=E4=BA=8B=E5=8A=A1=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=8F=92=E5=85=A5=E6=95=B0=E6=8D=AE=E7=9A=84=E9=99=90?= =?UTF-8?q?=E5=88=B6=E9=BB=98=E8=AE=A4=E5=80=BC=E4=B8=BAint.MaxValue,?= =?UTF-8?q?=E8=87=AA=E5=A2=9E=E5=AD=97=E6=AE=B5=E7=9B=B4=E6=8E=A5=E8=BF=94?= =?UTF-8?q?=E5=9B=9E0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.DbContext/DbSet/DbSet.cs | 2 + .../ClickHouse/ClickHouseTest1.cs | 81 +++-- .../ClickHouse/CollectDataEntity.cs | 167 +++++++++ .../FreeSql.Tests/FreeSql.Tests.csproj | 20 +- FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml | 120 +++++++ FreeSql/Extensions/FreeSqlGlobalExtensions.cs | 2 +- FreeSql/FreeSql.xml | 183 ---------- .../Internal/CommonProvider/InsertProvider.cs | 2 +- .../CommonProvider/InsertProviderAsync.cs | 6 +- .../Internal/CommonProvider/UpdateProvider.cs | 6 +- .../CommonProvider/UpdateProviderAsync.cs | 4 +- .../Curd/ClickHouseInsert.cs | 236 ++++++++++--- .../Curd/ClickHouseUpdate.cs | 316 +++++++++++++++++- .../FreeSql.Provider.ClickHouse.csproj | 5 +- 14 files changed, 869 insertions(+), 281 deletions(-) create mode 100644 FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs diff --git a/FreeSql.DbContext/DbSet/DbSet.cs b/FreeSql.DbContext/DbSet/DbSet.cs index 97266db6..a0326c5f 100644 --- a/FreeSql.DbContext/DbSet/DbSet.cs +++ b/FreeSql.DbContext/DbSet/DbSet.cs @@ -305,6 +305,7 @@ namespace FreeSql case DataType.KingbaseES: case DataType.OdbcKingbaseES: case DataType.ShenTong: + case DataType.ClickHouse: return true; default: if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) @@ -320,6 +321,7 @@ namespace FreeSql if (isThrow) throw new Exception($"不可添加,已存在于状态管理:{_db.OrmOriginal.GetEntityString(_entityType, data)}"); return false; } + if (_db.OrmOriginal.Ado.DataType == DataType.ClickHouse) return true; var idval = _db.OrmOriginal.GetEntityIdentityValueWithPrimary(_entityType, data); if (idval > 0) { diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs index 22cece67..b8f068cc 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -6,6 +6,7 @@ using Xunit; using System.Linq; using System.Collections; using System.Diagnostics; +using XY.Model.Business; namespace FreeSql.Tests.MySql { @@ -37,7 +38,7 @@ namespace FreeSql.Tests.MySql [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] public class TestClickHouse : IEnumerable { - [FreeSql.DataAnnotations.Column(IsPrimary = true)] + [FreeSql.DataAnnotations.Column(IsPrimary = true, IsIdentity = true)] [Now] public long Id { get; set; } @@ -67,8 +68,6 @@ namespace FreeSql.Tests.MySql g.clickHouse.Insert(item).ExecuteAffrows(); g.clickHouse.Aop.AuditValue -= audit; - - Assert.Equal(item.Id, id); } @@ -84,19 +83,36 @@ namespace FreeSql.Tests.MySql Stopwatch stopwatch =new Stopwatch(); var fsql = g.clickHouse; List list=new List(); - for (int i = 0; i < 1000000; i++) + List list1=new List(); + var date=DateTime.Now; + for (int i = 1; i < 1000000; i++) { - list.Add(new TestClickHouse() + //list.Add(new TestClickHouse + //{ + // Id=i, Name=i.ToString() + //}); + + list1.Add(new CollectDataEntity { - Id = i, - Name = $"测试{i}" + Id = new Random().Next(), + CollectTime = DateTime.Now, + DataFlag = "1", + EquipmentCode = "11", + Guid = "11111", + UnitStr = "111", + PropertyCode = "1111" }); } - fsql.Delete().Where(t => 1 == 1).ExecuteAffrows(); + fsql.Delete().Where(t => 1 == 1).ExecuteAffrows(); stopwatch.Start(); - fsql.Insert(list).ExecuteAffrows(); + var insert=fsql.Insert(list1); stopwatch.Stop(); - Debug.WriteLine(list.Count+"条用时:" +stopwatch.ElapsedMilliseconds.ToString()); + Debug.WriteLine("审计数据用时:" + stopwatch.ElapsedMilliseconds.ToString()); + stopwatch.Restart(); + insert.ExecuteAffrows(); + //fsql.GetRepository().Insert(list1); + stopwatch.Stop(); + Debug.WriteLine("转换并插入用时:" +stopwatch.ElapsedMilliseconds.ToString()); //var items = fsql.Select().Where(o=>o.Id>900).OrderByDescending(o=>o.Id).ToList(); //Assert.Equal(100, items.Count); } @@ -110,7 +126,7 @@ namespace FreeSql.Tests.MySql .Page(1,100) .Where(o=>o.Id>200&&o.Id<500) .Count(out var count).ToList(); - Assert.Equal(100, list.Count); + //Assert.Equal(100, list.Count); } [Fact] @@ -120,7 +136,7 @@ namespace FreeSql.Tests.MySql var count1=fsql.Select().Count(); fsql.Delete().Where(o => o.Id < 500).ExecuteAffrows(); var count2 = fsql.Select().Count(); - Assert.NotEqual(count1, count2); + //Assert.NotEqual(count1, count2); } [Fact] @@ -178,28 +194,43 @@ namespace FreeSql.Tests.MySql } - [Fact] - public void TestRepositoryUpdateTime() - { - //暂时无法修改 - var fsql = g.clickHouse; - var repository=fsql.GetRepository(); - var list = repository.Select.ToList(); - list.ForEach(o=>o.UpdateTime = DateTime.Now); - list.ForEach(o => o.Enable = true); - repository.Update(list); - - } [Fact] public void TestUpdateTime() { var fsql = g.clickHouse; - var state=fsql.GetRepository().UpdateDiy.Set(o=>o.UpdateTime,DateTime.Now).Where(o=>1==1).ExecuteAffrows(); + var state = fsql.GetRepository().UpdateDiy.Set(o => o.UpdateTime, DateTime.Now).Where(o => 1 == 1).ExecuteAffrows(); //var state1 = fsql.GetRepository().UpdateDiy.Set(o => o.UpdateTime, null).Where(o => 1 == 1).ExecuteAffrows(); } + [Fact] + public void TestRepositoryUpdateTime() + { + Stopwatch stopwatch = new Stopwatch(); + var fsql = g.clickHouse; + var repository=fsql.GetRepository(); + List list=new List(); + for (int i = 1; i < 5; i++) + { + list.Add(new TestAuditValue + { + Id = new Random().Next(), + Age=1, Name=i.ToString(), State=true, CreateTime=DateTime.Now, + UpdateTime=DateTime.Now, + Enable = false + }); + } + list = repository.Insert(list); + //var list = repository.Select.ToList(); + list.ForEach(o=>o.UpdateTime = DateTime.Now); + list.ForEach(o => o.Enable = true); + stopwatch.Start(); + repository.Update(list); + stopwatch.Stop(); + Debug.WriteLine("更新用时:" + stopwatch.ElapsedMilliseconds.ToString()); + + } } } diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs new file mode 100644 index 00000000..07abf7ff --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs @@ -0,0 +1,167 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; +using System; +using System.ComponentModel; +namespace XY.Model.Business +{ + /// + /// 实时数据 + /// + [Table(Name = "CollectData")] + [Index("idx_{tablename}_01", nameof(Guid), true)] + public partial class CollectDataEntity + { + + /// + /// Guid + /// + [Column(StringLength = 50)] + public string Guid { get; set; } + + /// + /// 租户Id + /// + [Description("租户Id")] + [Column(CanUpdate = false)] + public virtual long? TenantId { get; set; } + + /// + /// 版本 + /// + [Description("版本")] + [Column(IsVersion = false)] + public long Version { get; set; } + + /// + /// 是否删除 + /// + [Description("是否删除")] + [Column()] + public bool IsDeleted { get; set; } = false; + + /// + /// 创建者Id + /// + [Description("创建者Id")] + [Column(CanUpdate = false)] + public long? CreatedUserId { get; set; } + + /// + /// 创建者 + /// + [Description("创建者")] + [Column(CanUpdate = false, StringLength = 50)] + public string CreatedUserName { get; set; } + + /// + /// 创建时间 + /// + [Description("创建时间")] + [Column(CanUpdate = false, ServerTime = DateTimeKind.Local)] + public DateTime? CreatedTime { get; set; } + + /// + /// 修改者Id + /// + [Description("修改者Id")] + [Column(CanInsert = false)] + public long? ModifiedUserId { get; set; } + + /// + /// 修改者 + /// + [Description("修改者")] + [Column(CanInsert = false, StringLength = 50)] + public string ModifiedUserName { get; set; } + + /// + /// 修改时间 + /// + [Description("修改时间")] + [Column(CanInsert = false, ServerTime = DateTimeKind.Local)] + public DateTime? ModifiedTime { get; set; } + /// + /// 数据标识 + /// + [Description("数据标识")] + [Column(CanInsert = false, StringLength = 2)] + public string DataFlag { get; set; } + /// + /// 主键Id + /// + [Description("主键Id")] + [Column(Position = 1)] + public long Id { get; set; } + /// + /// 设备编号 + /// + [Column(StringLength = 50)] + public string EquipmentCode { get; set; } + + /// + /// 数据编号,如为空使用默认数据 + /// + [Column(StringLength = 50)] + public string PropertyCode { get; set; } + /// + /// 数据名称,如为空使用默认数据 + /// + [Column(StringLength = 50)] + public string PropertyName { get; set; } + + /// + /// 数值或状态是否变更 + /// + public bool IsValueOrStateChanged { get; set; } + + /// + /// 采集数值 + /// + public decimal? NumericValue { get; set; } + + /// + /// 备注 + /// + [Column(StringLength = 200)] + public string Remark { get; set; } + + /// + /// 服务标记 + /// + [Column(StringLength = 20)] + public string ServiceFlag { get; set; } + + /// + /// 状态 + /// + [Column(StringLength = 50)] + public string StrState { get; set; } + + /// + /// 文本数值 + /// + [Column(StringLength = 50)] + public string StrValue { get; set; } + + /// + /// 单位 + /// + [Column(StringLength = 10)] + public string UnitStr { get; set; } + + /// + /// 采集时间 + /// + public DateTime CollectTime { get; set; } + + + public string FieldKey + { + get + { + return EquipmentCode +"_"+ PropertyCode; + } + } + } + +} \ No newline at end of file diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj index 8f797408..a5deb8cb 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj @@ -14,16 +14,16 @@ - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml index 2f9add59..96d17f87 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml @@ -945,6 +945,126 @@ 新打印模块 + + + 实时数据 + + + + + Guid + + + + + 租户Id + + + + + 版本 + + + + + 是否删除 + + + + + 创建者Id + + + + + 创建者 + + + + + 创建时间 + + + + + 修改者Id + + + + + 修改者 + + + + + 修改时间 + + + + + 数据标识 + + + + + 主键Id + + + + + 设备编号 + + + + + 数据编号,如为空使用默认数据 + + + + + 数据名称,如为空使用默认数据 + + + + + 数值或状态是否变更 + + + + + 采集数值 + + + + + 备注 + + + + + 服务标记 + + + + + 状态 + + + + + 文本数值 + + + + + 单位 + + + + + 采集时间 + + 调价单 diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index 25da334b..3f67a8a5 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -58,7 +58,7 @@ public static partial class FreeSqlGlobalExtensions /// /// /// - internal static string DisplayCsharp(this Type type, bool isNameSpace = true) + public static string DisplayCsharp(this Type type, bool isNameSpace = true) { if (type == null) return null; if (type == typeof(void)) return "void"; diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 9c849a52..06ac1dc3 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -3172,177 +3172,6 @@ - - - 测试数据库是否连接正确,本方法执行如下命令: - MySql/SqlServer/PostgreSQL/达梦/人大金仓/神通: SELECT 1 - Oracle: SELECT 1 FROM dual - - 命令超时设置(秒) - - true: 成功, false: 失败 - - - - 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 - - - - - - - - - - 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 查询 - - - - - - - - - 查询,ExecuteArrayAsync("select * from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 查询 - - - - - - - - - 查询,ExecuteDataSetAsync("select * from user where age > @age; select 2", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 查询 - - - - - - - - - 查询,ExecuteDataTableAsync("select * from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 在【主库】执行 - - - - - - - - - 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 在【主库】执行 - - - - - - - - - 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new SqlParameter { ParameterName = "age", Value = 25 }) - - - - - - - - - - - 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - - - 执行SQL返回对象集合,Query<User>("select * from user where age > @age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) - - - - - - - - - - - - 执行SQL返回对象集合,Query<User, Address>("select * from user where age > @age; select * from address", new { age = 25 }) - 提示:parms 参数还可以传 Dictionary<string, object> - - - - - - - - 可自定义解析表达式 @@ -4216,12 +4045,6 @@ 超时 - - - 获取资源 - - - 使用完毕后,归还资源 @@ -4292,12 +4115,6 @@ 资源对象 - - - 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 - - 资源对象 - 归还对象给对象池的时候触发 diff --git a/FreeSql/Internal/CommonProvider/InsertProvider.cs b/FreeSql/Internal/CommonProvider/InsertProvider.cs index 5a2b62cb..21254fc0 100644 --- a/FreeSql/Internal/CommonProvider/InsertProvider.cs +++ b/FreeSql/Internal/CommonProvider/InsertProvider.cs @@ -210,7 +210,7 @@ namespace FreeSql.Internal.CommonProvider ret[a] = _source.GetRange(a * takeMax, Math.Min(takeMax, _source.Count - a * takeMax)); return ret; } - protected int SplitExecuteAffrows(int valuesLimit, int parameterLimit) + protected virtual int SplitExecuteAffrows(int valuesLimit, int parameterLimit) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = 0; diff --git a/FreeSql/Internal/CommonProvider/InsertProviderAsync.cs b/FreeSql/Internal/CommonProvider/InsertProviderAsync.cs index 828dba7d..1dfefd6b 100644 --- a/FreeSql/Internal/CommonProvider/InsertProviderAsync.cs +++ b/FreeSql/Internal/CommonProvider/InsertProviderAsync.cs @@ -17,7 +17,7 @@ namespace FreeSql.Internal.CommonProvider { #if net40 #else - async protected Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + async protected virtual Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = 0; @@ -96,7 +96,7 @@ namespace FreeSql.Internal.CommonProvider return ret; } - async protected Task SplitExecuteIdentityAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + async protected virtual Task SplitExecuteIdentityAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) { var ss = SplitSource(valuesLimit, parameterLimit); long ret = 0; @@ -177,7 +177,7 @@ namespace FreeSql.Internal.CommonProvider return ret; } - async protected Task> SplitExecuteInsertedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + async protected virtual Task> SplitExecuteInsertedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = new List(); diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs index e4250ebe..63b81baf 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProvider.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -138,7 +138,7 @@ namespace FreeSql.Internal.CommonProvider } #region 参数化数据限制,或values数量限制 - internal List[] SplitSource(int valuesLimit, int parameterLimit) + protected internal List[] SplitSource(int valuesLimit, int parameterLimit) { valuesLimit = valuesLimit - 1; parameterLimit = parameterLimit - 1; @@ -163,7 +163,7 @@ namespace FreeSql.Internal.CommonProvider ret[a] = _source.GetRange(a * takeMax, Math.Min(takeMax, _source.Count - a * takeMax)); return ret; } - protected int SplitExecuteAffrows(int valuesLimit, int parameterLimit) + protected virtual int SplitExecuteAffrows(int valuesLimit, int parameterLimit) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = 0; @@ -237,7 +237,7 @@ namespace FreeSql.Internal.CommonProvider return ret; } - protected List SplitExecuteUpdated(int valuesLimit, int parameterLimit) + protected virtual List SplitExecuteUpdated(int valuesLimit, int parameterLimit) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = new List(); diff --git a/FreeSql/Internal/CommonProvider/UpdateProviderAsync.cs b/FreeSql/Internal/CommonProvider/UpdateProviderAsync.cs index 32e97a6c..9f8aa53a 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProviderAsync.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProviderAsync.cs @@ -18,7 +18,7 @@ namespace FreeSql.Internal.CommonProvider { #if net40 #else - async protected Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + async protected virtual Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = 0; @@ -91,7 +91,7 @@ namespace FreeSql.Internal.CommonProvider ClearData(); return ret; } - async protected Task> SplitExecuteUpdatedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + async protected virtual Task> SplitExecuteUpdatedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) { var ss = SplitSource(valuesLimit, parameterLimit); var ret = new List(); diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs index 57f606bb..5666ca8c 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -33,9 +34,9 @@ namespace FreeSql.ClickHouse.Curd internal Dictionary InternalIgnore => _ignore; internal void InternalClearData() => ClearData(); - public override int ExecuteAffrows() => base.SplitExecuteAffrows(int.MaxValue, int.MaxValue); - public override long ExecuteIdentity() => base.SplitExecuteIdentity(int.MaxValue, int.MaxValue); - public override List ExecuteInserted() => base.SplitExecuteInserted(int.MaxValue, int.MaxValue); + public override int ExecuteAffrows() => SplitExecuteAffrows(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchParameterLimit > 0 ? _batchParameterLimit : int.MaxValue); + public override long ExecuteIdentity() => SplitExecuteIdentity(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchParameterLimit > 0 ? _batchParameterLimit : int.MaxValue); + public override List ExecuteInserted() => SplitExecuteInserted(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchParameterLimit > 0 ? _batchParameterLimit : int.MaxValue); public override string ToSql() @@ -54,6 +55,7 @@ namespace FreeSql.ClickHouse.Curd { try { + Debug.WriteLine($"开始执行时间:{DateTime.Now}"); before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, null, _params); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); using var bulkCopyInterface = new ClickHouseBulkCopy(_orm.Ado.MasterPool.Get().Value as ClickHouseConnection) @@ -61,7 +63,8 @@ namespace FreeSql.ClickHouse.Curd DestinationTableName = _table.DbName, BatchSize = _source.Count }; - bulkCopyInterface.WriteToServerAsync(ToDataTable(),default).Wait(); + var data=ToDataTable(); + bulkCopyInterface.WriteToServerAsync(data, default).Wait(); return affrows; } catch (Exception ex) @@ -82,7 +85,7 @@ namespace FreeSql.ClickHouse.Curd _orm.Aop.CurdBeforeHandler?.Invoke(this, before); try { - affrows = _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params); + affrows = _orm.Ado.ExecuteNonQuery(_connection, null, CommandType.Text, sql, _commandTimeout, _params); } catch (Exception ex) { @@ -122,28 +125,10 @@ namespace FreeSql.ClickHouse.Curd 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); - } + var identCols = _table.Columns.Where(a => a.Value.Attribute.IsIdentity == true); + if (identCols.Any()&&_source.Count==1) + ret = (long)identCols.First().Value.GetValue(_source.First()); return ret; } protected override List RawExecuteInserted() @@ -168,7 +153,7 @@ namespace FreeSql.ClickHouse.Curd Exception exception = null; try { - ret = _orm.Ado.Query(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, _params); + ret = _orm.Ado.Query(_table.TypeLazy ?? _table.Type, _connection, null, CommandType.Text, sql, _commandTimeout, _params); } catch (Exception ex) { @@ -185,23 +170,65 @@ namespace FreeSql.ClickHouse.Curd #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); + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); + public override Task ExecuteIdentityAsync(CancellationToken cancellationToken = default) => base.SplitExecuteIdentityAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); + public override Task> ExecuteInsertedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteInsertedAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); async protected override Task RawExecuteIdentityAsync(CancellationToken cancellationToken = default) { - var sql = this.ToSql(); - if (string.IsNullOrEmpty(sql)) return 0; + //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); + //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, null, 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 await Task.FromResult(ret); + } + + + protected override int SplitExecuteAffrows(int valuesLimit, int parameterLimit) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = 0; + if (ss.Any() == false) + { + ClearData(); + return ret; + } + if (ss.Length == 1) + { + _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = this.RawExecuteAffrows(); + ClearData(); + return ret; + } + var before = new Aop.TraceBeforeEventArgs("SplitExecuteAffrows", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); 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; + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret += this.RawExecuteAffrows(); + } } catch (Exception ex) { @@ -210,11 +237,142 @@ namespace FreeSql.ClickHouse.Curd } finally { - var after = new Aop.CurdAfterEventArgs(before, exception, ret); - _orm.Aop.CurdAfterHandler?.Invoke(this, after); + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); } + ClearData(); return ret; } + async protected override Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = 0; + if (ss.Any() == false) + { + ClearData(); + return ret; + } + if (ss.Length == 1) + { + _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = await this.RawExecuteAffrowsAsync(cancellationToken); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteAffrowsAsync", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret += await this.RawExecuteAffrowsAsync(cancellationToken); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + + async protected override Task SplitExecuteIdentityAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { + var ss = SplitSource(valuesLimit, parameterLimit); + long ret = 0; + if (ss.Any() == false) + { + ClearData(); + return ret; + } + if (ss.Length == 1) + { + _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = await this.RawExecuteIdentityAsync(cancellationToken); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteIdentityAsync", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + if (a < ss.Length - 1) await this.RawExecuteAffrowsAsync(cancellationToken); + else ret = await this.RawExecuteIdentityAsync(cancellationToken); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + + async protected override Task> SplitExecuteInsertedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = new List(); + if (ss.Any() == false) + { + ClearData(); + return ret; + } + if (ss.Length == 1) + { + _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = await this.RawExecuteInsertedAsync(cancellationToken); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteInsertedAsync", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret.AddRange(await this.RawExecuteInsertedAsync(cancellationToken)); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + async protected override Task> RawExecuteInsertedAsync(CancellationToken cancellationToken = default) { var sql = this.ToSql(); @@ -237,7 +395,7 @@ namespace FreeSql.ClickHouse.Curd Exception exception = null; try { - ret = await _orm.Ado.QueryAsync(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, _params, cancellationToken); + ret = await _orm.Ado.QueryAsync(_table.TypeLazy ?? _table.Type, _connection, null, CommandType.Text, sql, _commandTimeout, _params, cancellationToken); } catch (Exception ex) { diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index 16745836..703ff6af 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -22,20 +22,12 @@ namespace FreeSql.ClickHouse.Curd internal StringBuilder InternalSbSet => _set; internal StringBuilder InternalSbSetIncr => _setIncr; internal Dictionary InternalIgnore => _ignore; - public override string ToSql() - { - var sql = base.ToSql(); - sql = sql.Remove(0, 6).Insert(0, "ALTER TABLE"); - var index = sql.IndexOf(" SET "); - sql = sql.Remove(index, 5).Insert(index, " UPDATE "); - return sql; - } internal void InternalResetSource(List source) => _source = source; internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); internal void InternalToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) => ToSqlCaseWhenEnd(sb, col); - public override int ExecuteAffrows() => base.SplitExecuteAffrows(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); - public override List ExecuteUpdated() => base.SplitExecuteUpdated(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override int ExecuteAffrows() => SplitExecuteAffrows(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override List ExecuteUpdated() => SplitExecuteUpdated(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); protected override List RawExecuteUpdated() => throw new NotImplementedException("FreeSql.ClickHouse.Custom 未实现该功能 未实现该功能"); protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) { @@ -72,15 +64,315 @@ namespace FreeSql.ClickHouse.Curd } sb.Append(")"); } - protected override void ToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) + + public override string ToSql() { - sb.Insert(sb.Length - 4, $" ELSE {_commonUtils.QuoteSqlName(col.Attribute.Name)}"); + if (_where.Length == 0 && _source.Any() == false) return null; + + var sb = new StringBuilder(); + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(TableRuleInvoke())).Append(" UPDATE "); + + if (_set.Length > 0) + { //指定 set 更新 + sb.Append(_set.ToString().Substring(2)); + + } + else if (_source.Count == 1) + { //保存 Source + _paramsSource.Clear(); + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (col.Attribute.IsPrimary) continue; + if (_tempPrimarys.Any(a => a.CsName == col.CsName)) continue; + if (col.Attribute.IsIdentity == false && col.Attribute.IsVersion == false && _ignore.ContainsKey(col.Attribute.Name) == false) + { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = "); + + if (col.Attribute.CanUpdate && string.IsNullOrEmpty(col.DbUpdateValue) == false) + sb.Append(col.DbUpdateValue); + else + { + var val = col.GetDbValue(_source.First()); + + var colsql = _noneParameter ? _commonUtils.GetNoneParamaterSqlValue(_paramsSource, "u", col, col.Attribute.MapType, val) : + _commonUtils.QuoteWriteParamterAdapter(col.Attribute.MapType, _commonUtils.QuoteParamterName($"p_{_paramsSource.Count}")); + sb.Append(_commonUtils.RewriteColumn(col, colsql)); + if (_noneParameter == false) + _commonUtils.AppendParamter(_paramsSource, null, col, col.Attribute.MapType, val); + } + ++colidx; + } + } + if (colidx == 0) return null; + + } + else if (_source.Count > 1) + { //批量保存 Source + if (_tempPrimarys.Any() == false) return null; + + var caseWhen = new StringBuilder(); + ToSqlCase(caseWhen, _tempPrimarys); + var cw = $"{caseWhen.ToString()}="; + _paramsSource.Clear(); + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (col.Attribute.IsPrimary) continue; + if (_tempPrimarys.Any(a => a.CsName == col.CsName)) continue; + if (col.Attribute.IsIdentity == false && col.Attribute.IsVersion == false && _ignore.ContainsKey(col.Attribute.Name) == false) + { + if (colidx > 0) sb.Append(", "); + var columnName = _commonUtils.QuoteSqlName(col.Attribute.Name); + sb.Append(columnName).Append(" = "); + + if (col.Attribute.CanUpdate && string.IsNullOrEmpty(col.DbUpdateValue) == false) + sb.Append(col.DbUpdateValue); + else + { + var nulls = 0; + var cwsb = new StringBuilder().Append(" multiIf( "); + foreach (var d in _source) + { + cwsb.Append(cw); + ToSqlWhen(cwsb, _tempPrimarys, d); + cwsb.Append(","); + var val = col.GetDbValue(d); + + var colsql = _noneParameter ? _commonUtils.GetNoneParamaterSqlValue(_paramsSource, "u", col, col.Attribute.MapType, val) : + _commonUtils.QuoteWriteParamterAdapter(col.Attribute.MapType, _commonUtils.QuoteParamterName($"p_{_paramsSource.Count}")); + cwsb.Append(_commonUtils.RewriteColumn(col, colsql)); + if (_noneParameter == false) + _commonUtils.AppendParamter(_paramsSource, null, col, col.Attribute.MapType, val); + if (val == null || val == DBNull.Value) nulls++; + cwsb.Append(", "); + } + if (nulls == _source.Count) sb.Append("NULL"); + else + { + cwsb.Append(columnName).Append(" )"); + ToSqlCaseWhenEnd(cwsb, col); + sb.Append(cwsb); + } + cwsb.Clear(); + } + ++colidx; + } + } + if (colidx == 0) return null; + } + else if (_setIncr.Length == 0) + return null; + + if (_setIncr.Length > 0) + sb.Append(_set.Length > 0 ? _setIncr.ToString() : _setIncr.ToString().Substring(2)); + + if (_source.Any() == false) + { + foreach (var col in _table.Columns.Values) + if (col.Attribute.CanUpdate && string.IsNullOrEmpty(col.DbUpdateValue) == false) + sb.Append(", ").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = ").Append(col.DbUpdateValue); + } + + if (_table.VersionColumn != null) + { + var vcname = _commonUtils.QuoteSqlName(_table.VersionColumn.Attribute.Name); + if (_table.VersionColumn.Attribute.MapType == typeof(byte[])) + { + _updateVersionValue = Utils.GuidToBytes(Guid.NewGuid()); + sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.GetNoneParamaterSqlValue(_paramsSource, "uv", _table.VersionColumn, _table.VersionColumn.Attribute.MapType, _updateVersionValue)); + } + else + sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.IsNull(vcname, 0)).Append(" + 1"); + } + + sb.Append(" \r\nWHERE "); + if (_source.Any()) + { + if (_tempPrimarys.Any() == false) throw new ArgumentException($"{_table.Type.DisplayCsharp()} 没有定义主键,无法使用 SetSource,请尝试 SetDto"); + sb.Append('(').Append(_commonUtils.WhereItems(_tempPrimarys, "", _source)).Append(')'); + } + + if (_where.Length > 0) + sb.Append(_source.Any() ? _where.ToString() : _where.ToString().Substring(5)); + + if (_whereGlobalFilter.Any()) + { + var globalFilterCondi = _commonExpression.GetWhereCascadeSql(new SelectTableInfo { Table = _table }, _whereGlobalFilter, false); + if (string.IsNullOrEmpty(globalFilterCondi) == false) + sb.Append(" AND ").Append(globalFilterCondi); + } + + if (_table.VersionColumn != null) + { + var versionCondi = WhereCaseSource(_table.VersionColumn.CsName, sqlval => sqlval); + if (string.IsNullOrEmpty(versionCondi) == false) + sb.Append(" AND ").Append(versionCondi); + } + + _interceptSql?.Invoke(sb); + return sb.ToString(); } + + protected override int SplitExecuteAffrows(int valuesLimit, int parameterLimit) + { + var ss = base.SplitSource(valuesLimit, parameterLimit); + var ret = 0; + if (ss.Length <= 1) + { + if (_source?.Any() == true) _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = this.RawExecuteAffrows(); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteAffrows", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret += this.RawExecuteAffrows(); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + protected override List SplitExecuteUpdated(int valuesLimit, int parameterLimit) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = new List(); + if (ss.Length <= 1) + { + if (_source?.Any() == true) _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = this.RawExecuteUpdated(); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteUpdated", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret.AddRange(this.RawExecuteUpdated()); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + + #if net40 #else public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); public override Task> ExecuteUpdatedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteUpdatedAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); protected override Task> RawExecuteUpdatedAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException("FreeSql.ClickHouse.Custom 未实现该功能 未实现该功能"); + + async protected override Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = 0; + if (ss.Length <= 1) + { + if (_source?.Any() == true) _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = await this.RawExecuteAffrowsAsync(cancellationToken); + ClearData(); + return ret; + } + + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteAffrowsAsync", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret += await this.RawExecuteAffrowsAsync(cancellationToken); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + async protected override Task> SplitExecuteUpdatedAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { + var ss = SplitSource(valuesLimit, parameterLimit); + var ret = new List(); + if (ss.Length <= 1) + { + if (_source?.Any() == true) _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); + ret = await this.RawExecuteUpdatedAsync(cancellationToken); + ClearData(); + return ret; + } + + var before = new Aop.TraceBeforeEventArgs("SplitExecuteUpdatedAsync", null); + _orm.Aop.TraceBeforeHandler?.Invoke(this, before); + Exception exception = null; + try + { + for (var a = 0; a < ss.Length; a++) + { + _source = ss[a]; + _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); + ret.AddRange(await this.RawExecuteUpdatedAsync(cancellationToken)); + } + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.TraceAfterEventArgs(before, null, exception); + _orm.Aop.TraceAfterHandler?.Invoke(this, after); + } + ClearData(); + return ret; + } + #endif } } diff --git a/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj index 83ff3325..43b24148 100644 --- a/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj +++ b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj @@ -1,11 +1,12 @@ + netstandard2.1 2.6.100 true - FreeSql;ncc;YeXiangQin - FreeSql 数据库自定义适配,访问所有数据库 + FreeSql;ncc;YeXiangQin;ChenBo + FreeSql 数据库实现,基于 ClickHouse.Client Ado.net https://github.com/2881099/FreeSql https://github.com/2881099/FreeSql git From 0212577933ee60b3b9f95a69e2450fe417839038 Mon Sep 17 00:00:00 2001 From: chenbo <1114927184@qq.com> Date: Thu, 2 Dec 2021 16:33:48 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E6=88=96=E4=BF=AE=E6=94=B9=E6=97=B6=E7=94=B1?= =?UTF-8?q?=E4=BA=8E=E6=97=A0=E6=B3=95=E8=8E=B7=E5=8F=96=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=AF=BC=E8=87=B4=E7=9A=84=E6=97=A0=E9=99=90?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=EF=BC=8C=E6=B7=BB=E5=8A=A0decimal=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=B1=BB=E5=9E=8B=EF=BC=88=E6=9C=80=E5=90=8E=E4=B8=80?= =?UTF-8?q?=E4=BD=8D=E5=B0=8F=E6=95=B0=E7=82=B9=E7=B2=BE=E5=BA=A6=E4=BC=9A?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.DbContext/FreeSql.DbContext.xml | 9 + .../ClickHouse/ClickHouseTest1.cs | 43 +++- .../ClickHouse/CollectDataEntity.cs | 3 +- FreeSql/FreeSql.xml | 183 ++++++++++++++++++ .../ClickHouseCodeFirst.cs | 8 +- .../ClickHouseDbFirst.cs | 4 + .../ClickHouseExpression.cs | 6 +- .../Curd/ClickHouseInsert.cs | 13 +- .../Curd/ClickHouseUpdate.cs | 11 +- 9 files changed, 257 insertions(+), 23 deletions(-) diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index da7ace6b..bdd16ff9 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -538,5 +538,14 @@ + + + 批量注入 Repository,可以参考代码自行调整 + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs index b8f068cc..28f1ca01 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest1.cs @@ -36,18 +36,14 @@ namespace FreeSql.Tests.MySql public int? Points { get; set; } } [FreeSql.DataAnnotations.Table(Name = "ClickHouseTest")] - public class TestClickHouse : IEnumerable + public class TestClickHouse { [FreeSql.DataAnnotations.Column(IsPrimary = true, IsIdentity = true)] [Now] public long Id { get; set; } public string Name { get; set; } - public IEnumerator GetEnumerator() - { - yield return Id; - yield return Name; - } + public Decimal Money { get; set; } } class NowAttribute: Attribute { } @@ -231,6 +227,41 @@ namespace FreeSql.Tests.MySql Debug.WriteLine("更新用时:" + stopwatch.ElapsedMilliseconds.ToString()); } + [Fact] + public async void TestInsertUpdateData() + { + //g.clickHouse.CodeFirst.SyncStructure(); + Stopwatch stopwatch = new Stopwatch(); + var fsql = g.clickHouse; + var repository=fsql.GetRepository(); + await repository.DeleteAsync(o=>o.Id>0); + List tables = new List(); + for (int i = 1; i < 3; i++) + { + tables.Add(new CollectDataEntity + { + Id = new Random().Next(), + CollectTime = DateTime.Now, + DataFlag = "1", + EquipmentCode = "11", + UnitStr = "111", + PropertyCode = "1111", + NumericValue=1111.1119999912500M + }); + } + + var insert = repository.Orm.Insert(tables); + insert.ExecuteAffrows(); + var list = repository.Orm.Select().ToList(); + //var list = repository.Insert(tables); + //var list = repository.Select.ToList(); + //list.ForEach(o=>o.EquipmentCode = "666"); + //stopwatch.Start(); + //await repository.UpdateAsync(list); + //stopwatch.Stop(); + Debug.WriteLine("更新用时:" + stopwatch.ElapsedMilliseconds.ToString()); + + } } } diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs index 07abf7ff..0e8548e1 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/CollectDataEntity.cs @@ -113,10 +113,11 @@ namespace XY.Model.Business /// 数值或状态是否变更 /// public bool IsValueOrStateChanged { get; set; } - + /// /// 采集数值 /// + [Column(StringLength = 18)] public decimal? NumericValue { get; set; } /// diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 06ac1dc3..9c849a52 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -3172,6 +3172,177 @@ + + + 测试数据库是否连接正确,本方法执行如下命令: + MySql/SqlServer/PostgreSQL/达梦/人大金仓/神通: SELECT 1 + Oracle: SELECT 1 FROM dual + + 命令超时设置(秒) + + true: 成功, false: 失败 + + + + 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + + + + + + + + + + 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteArrayAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataSetAsync("select * from user where age > @age; select 2", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataTableAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > @age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + + 执行SQL返回对象集合,Query<User, Address>("select * from user where age > @age; select * from address", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + 可自定义解析表达式 @@ -4045,6 +4216,12 @@ 超时 + + + 获取资源 + + + 使用完毕后,归还资源 @@ -4115,6 +4292,12 @@ 资源对象 + + + 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 + + 资源对象 + 归还对象给对象池的时候触发 diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs index da491174..8f006685 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs @@ -35,6 +35,7 @@ namespace FreeSql.ClickHouse { typeof(double).FullName, CsToDb.New(DbType.Double, "Float64", "Float64", false, false, 0) },{ typeof(double?).FullName, CsToDb.New(DbType.Double, "Float64", "Nullable(Float64)", false, true, null) }, { typeof(float).FullName, CsToDb.New(DbType.Single, "Float32","Float32", false, false, 0) },{ typeof(float?).FullName, CsToDb.New(DbType.Single, "Float32","Nullable(Float32)", false, true, null) }, + { typeof(decimal).FullName, CsToDb.New(DbType.Decimal, "Decimal128(19)","Decimal128(19)", false, false, 0) },{ typeof(decimal?).FullName, CsToDb.New(DbType.Decimal, "Nullable(Decimal128(19))","Nullable(Decimal128(19))", false, true, null) }, { typeof(DateTime).FullName, CsToDb.New(DbType.DateTime, "DateTime('Asia/Shanghai')", "DateTime('Asia/Shanghai')", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName, CsToDb.New(DbType.DateTime, "DateTime('Asia/Shanghai')", "Nullable(DateTime('Asia/Shanghai'))", false, true, null) }, @@ -114,13 +115,14 @@ namespace FreeSql.ClickHouse foreach (var uk in tb.Indexes) { sb.Append(" \r\n "); - sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))).Append("("); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, tbname[1]))); foreach (var tbcol in uk.Columns) { + sb.Append(" "); sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); - sb.Append("TYPE set(8192) GRANULARITY 5, "); + sb.Append("TYPE set(8192) GRANULARITY 5, "); } - sb.Remove(sb.Length - 2, 2).Append("),"); + sb.Remove(sb.Length - 2, 2); } sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) "); diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs index c0c6da19..92e57371 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseDbFirst.cs @@ -67,6 +67,9 @@ namespace FreeSql.ClickHouse case "Float32": case "float": case "nullable(float32)": return DbType.Single; + case "decimal": + case "decimal128": + case "nullable(decimal128)": return DbType.Decimal; case "date": case "nullable(date)": return DbType.Date; case "datetime": @@ -103,6 +106,7 @@ namespace FreeSql.ClickHouse { (int)DbType.Double, new DbToCs("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, { (int)DbType.Single, new DbToCs("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + { (int)DbType.Decimal, new DbToCs("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, { (int)DbType.Date, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, { (int)DbType.Date, new DbToCs("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs index ba173483..dc5ab194 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs @@ -35,7 +35,7 @@ namespace FreeSql.ClickHouse case "System.Byte": return $"cast({getExp(operandExp)} as Int8)"; case "System.Char": return $"substr(cast({getExp(operandExp)} as String), 1, 1)"; case "System.DateTime": return $"cast({getExp(operandExp)} as DateTime)"; - case "System.Decimal": return $"cast({getExp(operandExp)} as Float64)"; + case "System.Decimal": return $"cast({getExp(operandExp)} as Decimal128(19))"; case "System.Double": return $"cast({getExp(operandExp)} as Float64)"; case "System.Int16": return $"cast({getExp(operandExp)} as Int16)"; case "System.Int32": return $"cast({getExp(operandExp)} as Int32)"; @@ -63,7 +63,7 @@ namespace FreeSql.ClickHouse case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} as Int8)"; case "System.Char": return $"substr(cast({getExp(callExp.Arguments[0])} as String), 1, 1)"; case "System.DateTime": return $"cast({getExp(callExp.Arguments[0])} as DateTime)"; - case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as Float64)"; + case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as Decimal128(19))"; case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as Float64)"; case "System.Int16": return $"cast({getExp(callExp.Arguments[0])} as Int16)"; case "System.Int32": return $"cast({getExp(callExp.Arguments[0])} as Int32)"; @@ -556,7 +556,7 @@ namespace FreeSql.ClickHouse case "ToByte": return $"cast({getExp(exp.Arguments[0])} as Int8)"; case "ToChar": return $"substr(cast({getExp(exp.Arguments[0])} as String), 1, 1)"; case "ToDateTime": return $"cast({getExp(exp.Arguments[0])} as DateTime)"; - case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as Float64)"; + case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as Decimal128(19))"; case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as Float64)"; case "ToInt16": case "ToInt32": diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs index 5666ca8c..ce6b2f9c 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -55,7 +55,6 @@ namespace FreeSql.ClickHouse.Curd { try { - Debug.WriteLine($"开始执行时间:{DateTime.Now}"); before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, null, _params); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); using var bulkCopyInterface = new ClickHouseBulkCopy(_orm.Ado.MasterPool.Get().Value as ClickHouseConnection) @@ -170,9 +169,9 @@ namespace FreeSql.ClickHouse.Curd #if net40 #else - public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); - public override Task ExecuteIdentityAsync(CancellationToken cancellationToken = default) => base.SplitExecuteIdentityAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); - public override Task> ExecuteInsertedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteInsertedAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => SplitExecuteAffrowsAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); + public override Task ExecuteIdentityAsync(CancellationToken cancellationToken = default) => SplitExecuteIdentityAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); + public override Task> ExecuteInsertedAsync(CancellationToken cancellationToken = default) => SplitExecuteInsertedAsync(_batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, _batchValuesLimit > 0 ? _batchValuesLimit : int.MaxValue, cancellationToken); async protected override Task RawExecuteIdentityAsync(CancellationToken cancellationToken = default) { @@ -255,7 +254,8 @@ namespace FreeSql.ClickHouse.Curd if (ss.Length == 1) { _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); - ret = await this.RawExecuteAffrowsAsync(cancellationToken); + await this.RawExecuteAffrowsAsync(cancellationToken); + ret = _source.Count; ClearData(); return ret; } @@ -269,7 +269,8 @@ namespace FreeSql.ClickHouse.Curd { _source = ss[a]; _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); - ret += await this.RawExecuteAffrowsAsync(cancellationToken); + await this.RawExecuteAffrowsAsync(cancellationToken); + ret += _source.Count; } } catch (Exception ex) diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index 703ff6af..9e69b3c9 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -293,18 +293,20 @@ namespace FreeSql.ClickHouse.Curd #if net40 #else - public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); - public override Task> ExecuteUpdatedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteUpdatedAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task> ExecuteUpdatedAsync(CancellationToken cancellationToken = default) => SplitExecuteUpdatedAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); protected override Task> RawExecuteUpdatedAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException("FreeSql.ClickHouse.Custom 未实现该功能 未实现该功能"); async protected override Task SplitExecuteAffrowsAsync(int valuesLimit, int parameterLimit, CancellationToken cancellationToken = default) + { var ss = SplitSource(valuesLimit, parameterLimit); var ret = 0; if (ss.Length <= 1) { if (_source?.Any() == true) _batchProgress?.Invoke(new BatchProgressStatus(_source, 1, 1)); - ret = await this.RawExecuteAffrowsAsync(cancellationToken); + await this.RawExecuteAffrowsAsync(cancellationToken); + ret = _source.Count; ClearData(); return ret; } @@ -319,7 +321,8 @@ namespace FreeSql.ClickHouse.Curd { _source = ss[a]; _batchProgress?.Invoke(new BatchProgressStatus(_source, a + 1, ss.Length)); - ret += await this.RawExecuteAffrowsAsync(cancellationToken); + await this.RawExecuteAffrowsAsync(cancellationToken); + ret += _source.Count; } } catch (Exception ex)