diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest2.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest2.cs index 9bc76f41..b96e8e9d 100644 --- a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest2.cs @@ -8,11 +8,14 @@ using System.Diagnostics; using System.ComponentModel.DataAnnotations; using FreeSql.DataAnnotations; using Xunit; +using Xunit.Abstractions; namespace FreeSql.Tests.ClickHouse { public class ClickHouseTest2 { + + private static IFreeSql fsql = new FreeSqlBuilder().UseConnectionString(DataType.ClickHouse, "Host=127.0.0.1;Port=8123;Database=test;Compress=True;Min Pool Size=1") .UseMonitorCommand(cmd => Console.WriteLine($"线程:{cmd.CommandText}\r\n")) @@ -29,6 +32,7 @@ namespace FreeSql.Tests.ClickHouse { fsql.CodeFirst.SyncStructure(typeof(PositionInfoModel)); } + [Fact] public void Issuse1587TestOnePrimary() { diff --git a/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest3.cs b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest3.cs new file mode 100644 index 00000000..b3fbd72d --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/ClickHouse/ClickHouseTest3.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using FreeSql.DataAnnotations; +using Newtonsoft.Json; +using Xunit; +using Xunit.Abstractions; + +namespace FreeSql.Tests.ClickHouse +{ + public class ClickHouseTest3 + { + private static ITestOutputHelper _output; + private static IFreeSql _fsql; + + public ClickHouseTest3(ITestOutputHelper output) + { + _output = output; + _fsql = new FreeSqlBuilder().UseConnectionString(DataType.ClickHouse, + "Host=192.168.1.123;Port=8123;Database=test;Compress=True;Min Pool Size=1") + .UseMonitorCommand(cmd => _output.WriteLine($"线程:{cmd.CommandText}\r\n")) + .UseNoneCommandParameter(true) + .Build(); + } + + /// + /// 测试bool类型映射 + /// + [Fact] + public void TestBoolMappingSync() + { + _fsql.CodeFirst.SyncStructure(typeof(BoolMappingTest)); + } + + /// + /// 测试bool类型插入 + /// + [Fact] + public void TestBoolMappingInsert() + { + _fsql.Insert(new BoolMappingTest + { + Name = "Tom", + Age = 20, + Id = Guid.NewGuid().ToString(), + IsDelete = true, + IsEnable = true + }).ExecuteAffrows(); + + _fsql.Insert(new BoolMappingTest + { + Name = "Jess", + Age = 21, + Id = Guid.NewGuid().ToString(), + IsDelete = true, + IsEnable = false + }).ExecuteAffrows(); + + _fsql.Insert(new BoolMappingTest + { + Name = "Daily", + Age = 22, + Id = Guid.NewGuid().ToString(), + IsDelete = false, + IsEnable = false + }).ExecuteAffrows(); + } + + /// + /// 测试bool类型修改 + /// + [Fact] + public void TestBoolMappingUpdateSet() + { + _fsql.Update() + .Set(t => t.IsDelete, true) + .Where(b => b.Age > 10) + .ExecuteAffrows(); + } + + /// + /// 测试bool类型修改 + /// + [Fact] + public void TestBoolMappingUpdate() + { + _fsql.Update() + .SetSource(new BoolMappingTest + { + Id = "af199304-239a-48da-9c75-1d5e36167d74", + IsEnable = false, + IsDelete = true, + Age = 60, + Name = "Update" + }) + .ExecuteAffrows(); + } + + + /// + /// 测试bool类型查询 + /// + [Fact] + public void TestBoolMappingSelect() + { + var list = _fsql.Select().ToList(); + } + + /// + /// 测试Array类型映射 + /// + [Fact] + public void ArrayBoolMappingSync() + { + _fsql.CodeFirst.SyncStructure(typeof(ArrayMappingTestSimple)); + } + + /// + /// 测试Array类型插入 + /// + [Fact] + public void ArrayBoolMappingInsert() + { + var source = new List() + { + new() + { + Name = "daily", + Tags1 = Array.Empty(), + Tags2 = new[] { 3, 45, 100, 400 }, + Tags3 = new[] { false, true, false } + } + }; + var str = _fsql.Insert(source).ExecuteAffrows(); + } + + /// + /// 测试Array类型映射 + /// + [Fact] + public void ArrayBoolMappingSelect() + { + var list = _fsql.Select().ToList(); + _output.WriteLine(JsonConvert.SerializeObject(list)); + } + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectAnySync() + { + var sql = _fsql.Select().Where(a => !a.Tags1.Any()).ToList(a => a.Name); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + } + + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectLengthSync() + { + var sql = _fsql.Select().ToList(a => a.Tags1.Count()); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + + var sql2 = _fsql.Select().Where(a => a.Tags1.Count() > 5).ToList(a => a.Tags1); + _output.WriteLine(JsonConvert.SerializeObject(sql2)); + } + + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectContainsSync() + { + var sql = _fsql.Select().ToList(a => a.Tags1.Contains("a")); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + + var sql2 = _fsql.Select().Where(a => a.Tags2.Contains(2)).ToList(a => a.Tags2); + _output.WriteLine(JsonConvert.SerializeObject(sql2)); + } + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectConcatSync() + { + var list = new List() { "f" }; + var sql = _fsql.Select().ToList(a => a.Tags1.Concat(list)); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + } + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectConstContainsSync() + { + var list = new List() { "daily", "a" }; + var sql = _fsql.Select().Where(a => list.Contains(a.Name)).ToList(); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + } + + /// + /// 测试Array常用查询函数 + /// + [Fact] + public void ArraySelectConstLengthSync() + { + var sql = _fsql.Select().ToList(a => "aaaa".Length); + _output.WriteLine(JsonConvert.SerializeObject(sql)); + } + + /// + /// 测试ArrayFilter测试 + /// + [Fact] + public void ArrayFilterFuncTest() + { + //var list = _fsql.Select().Where(a => a.Tags2.ArrayFilter(o => o == 1).Any()) + // .ToSql(); + + + ////SELECT a.`name`, a.`tags1`, a.`tags2`, a.`tags3` + ////FROM `table_test_array_simple` a + ////WHERE (arrayFilter(x -> x = '1', a.`tags2`) != []) + + //_output.WriteLine(JsonConvert.SerializeObject(list)); + } + + /// + /// 测试ArrayFilter测试 + /// + [Fact] + public void IsPrimaryTest() + { + _fsql.CodeFirst.SyncStructure(); + } + + /// + /// https://github.com/dotnetcore/FreeSql/issues/969 + /// + [Fact] + public async Task UriStringIsTooLongTest() + { + _fsql.CodeFirst.SyncStructure(); + var json = + "[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}][{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}[{\"date\":\"2021-12-19T02:47:53.4365075 08:00\",\"temperatureC\":6,\"temperatureF\":42,\"summary\":\"Balmy\"},{\"date\":\"2021-12-20T02:47:53.4366893 08:00\",\"temperatureC\":36,\"temperatureF\":96,\"summary\":\"Bracing\"},{\"date\":\"2021-12-21T02:47:53.4366903 08:00\",\"temperatureC\":-15,\"temperatureF\":6,\"summary\":\"Bracing\"},{\"date\":\"2021-12-22T02:47:53.4366904 08:00\",\"temperatureC\":14,\"temperatureF\":57,\"summary\":\"Cool\"},{\"date\":\"2021-12-23T02:47:53.4366905 08:00\",\"temperatureC\":29,\"temperatureF\":84,\"summary\":\"Mild\"}"; + + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + json += json; + + var t = new TestTable + { + Id = Guid.NewGuid().ToString(), + Content = json, + Content2 = json, + Time = DateTime.Now + }; + + //单个插入报错 + await _fsql.Insert(t).ExecuteAffrowsAsync(); + + // await _fsql.Insert(t).ExecuteBulkCopyAsync(); + } + + + /// + /// 测试BulkCopy单条 + /// + /// + [Fact] + public async Task TestBulkCopySingle() + { + var t = new TestTable + { + Id = Guid.NewGuid().ToString(), + Content = "1", + Content2 = "2", + Time = DateTime.Now + }; + + //单个插入报错 + await _fsql.Insert(t).ExecuteAffrowsAsync(); + + await _fsql.Insert(t).ExecuteBulkCopyAsync(); + + _fsql.Insert(t).ExecuteBulkCopy(); + } + + /// + /// 测试BulkCopy多条 + /// + /// + [Fact] + public async Task TestBulkCopyMany() + { + var t = new List(); + + foreach (var i in Enumerable.Range(0, 10)) + { + t.Add(new TestTable + { + Id = Guid.NewGuid().ToString(), + Content = i.ToString(), + Content2 = i.ToString(), + Time = DateTime.Now + }); + } + + //单个插入报错 + await _fsql.Insert(t).ExecuteAffrowsAsync(); + + //BulkCopy不会报错 + await _fsql.Insert(t).ExecuteBulkCopyAsync(); + + _fsql.Insert(t).ExecuteBulkCopy(); + } + } + + + [Table(Name = "table_test_bool")] + public class BoolMappingTest + { + [Column(IsPrimary = true, Name = "id")] + public string Id { set; get; } + + [Column(Name = "name")] public string Name { get; set; } + + [Column(Name = "age")] public int Age { get; set; } + + [Column(Name = "is_delete")] public bool IsDelete { get; set; } + + [Column(Name = "is_enable")] public bool? IsEnable { get; set; } + } + + [Table(Name = "table_test_array")] + public class ArrayMappingTest + { + [Column(Name = "name", IsPrimary = true)] + public string Name { get; set; } + + [Column(Name = "tags1")] public IEnumerable Tags1 { get; set; } + + [Column(Name = "tags2")] public IList Tags2 { get; set; } + + [Column(Name = "tags3")] public List Tags3 { get; set; } + + [Column(Name = "tags4")] public ArrayList Tags4 { get; set; } + + [Column(Name = "tags5")] public Array Tags5 { get; set; } + + [Column(Name = "tags6")] public List Tags6 { get; set; } + + [Column(Name = "tags7")] public IEnumerable Tags7 { get; set; } + } + + [Table(Name = "table_test_array_simple")] + public class ArrayMappingTestSimple + + { + [Column(Name = "name", IsPrimary = true)] + public string Name { get; set; } + + [Column(Name = "tags1")] public string[] Tags1 { get; set; } + + [Column(Name = "tags2")] public int[] Tags2 { get; set; } + + [Column(Name = "tags3")] public bool[] Tags3 { get; set; } + } + + /// + /// Http请求信息统计 + /// + [Table(Name = "http_context_record")] + public class HttpContextRecord + { + [Column(Name = "id", IsPrimary = true)] + public string Id { get; set; } + + /// + /// 请求模板 + /// + [Column(Name = "request_total_key", StringLength = 80)] + public string RequestTotalKey { get; set; } + + /// + /// 请求量 + /// + [Column(Name = "total")] + public long Total { get; set; } + + /// + /// 记录请求类型 + /// + [Column(Name = "type")] + public int Type { get; set; } + + /// + /// 添加时间 + /// + [Column(Name = "add_time")] + public DateTime AddTime { get; set; } + } + + public class ContentRecord + { + [Column(IsPrimary = true)] public string Id { get; set; } + + public string? Content1 { get; set; } + + public string? Content2 { get; set; } + + public string? Content3 { get; set; } + + public string Content4 { get; set; } + } + + internal class TestTable + { + [Required] [Column(IsIdentity = true)] public string Id { get; set; } + + [Column(StringLength = -2)] public string Content { get; set; } + + [Column(StringLength = -2)] public string Content2 { get; set; } + + [Column(DbType = "DateTime64(3, 'Asia/Shanghai')")] + public DateTime Time { get; set; } + + public override string ToString() + { + return $"Id:{Id} Content:{Content} Content2:{Content2} Time:{Time}"; + } + } +} \ No newline at end of file diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml index 23fd15f0..47cc24ad 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml @@ -4,6 +4,128 @@ FreeSql.Tests + + + 测试bool类型映射 + + + + + 测试bool类型插入 + + + + + 测试bool类型修改 + + + + + 测试bool类型修改 + + + + + 测试bool类型查询 + + + + + 测试Array类型映射 + + + + + 测试Array类型插入 + + + + + 测试Array类型映射 + + + + + 测试Array常用查询函数 + + + + + 测试Array常用查询函数 + + + + + 测试Array常用查询函数 + + + + + 测试Array常用查询函数 + + + + + 测试Array常用查询函数 + + + + + 测试Array常用查询函数 + + + + + 测试ArrayFilter测试 + + + + + 测试ArrayFilter测试 + + + + + https://github.com/dotnetcore/FreeSql/issues/969 + + + + + 测试BulkCopy单条 + + + + + + 测试BulkCopy多条 + + + + + + Http请求信息统计 + + + + + 请求模板 + + + + + 请求量 + + + + + 记录请求类型 + + + + + 添加时间 + + 实时数据 diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs index a5461a4d..001c2b4e 100644 --- a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs @@ -43,7 +43,7 @@ namespace FreeSql.Internal.CommonProvider sb.Append(AddslashesProcessParam(z, mapType, mapColumn)); } - return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString(); + return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "[").Append("]").ToString(); } public static bool IsFromSlave(string cmdText) diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs index 3ba93410..d3de56e4 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseAdo/ClickHouseAdo.cs @@ -48,7 +48,7 @@ namespace FreeSql.ClickHouse param = Utils.GetDataReaderValue(mapType, param); if (param is bool || param is bool?) - return (bool)param ? 1 : 0; + return (bool)param; //不需要转0/1 else if (param is string) return string.Concat("'", param.ToString().Replace("'", "''").Replace("\\", "\\\\"), "'"); //只有 mysql 需要处理反斜杠 else if (param is char) diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs index e4cf8216..f56737fd 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseCodeFirst.cs @@ -1,6 +1,7 @@ using FreeSql.Internal; using FreeSql.Internal.Model; using System; +using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; @@ -23,8 +24,8 @@ namespace FreeSql.ClickHouse 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(bool).FullName, CsToDb.New(DbType.SByte, "Bool", "Bool", null, false, false) }, + { typeof(bool?).FullName, CsToDb.New(DbType.SByte, "Bool", "Nullable(Bool)", 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) }, @@ -50,7 +51,8 @@ namespace FreeSql.ClickHouse { typeof(float?).FullName, CsToDb.New(DbType.Single, "Float32", "Nullable(Float32)", false, true, null) }, { typeof(decimal).FullName, - CsToDb.New(DbType.Decimal, "Decimal(38, 19)", "Decimal(38, 19)", false, false, 0) //Nullable(Decimal(38, 19)) + CsToDb.New(DbType.Decimal, "Decimal(38, 19)", "Decimal(38, 19)", false, false, + 0) //Nullable(Decimal(38, 19)) }, { typeof(decimal?).FullName, @@ -75,17 +77,84 @@ namespace FreeSql.ClickHouse { 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; + + //判断是否是集合 + var isCollection = IsArray(type); + if (isCollection.Item1) + { + var genericType = isCollection.Item2; + var genericTypeName = genericType?.FullName; + var tryGetValue = _dicCsToDb.TryGetValue(genericTypeName, out var value); + if (tryGetValue) + { + var arrayDbType = $"Array({value.dbtype})"; + var defaultArray = new ArrayList(0); + return new DbInfoResult(Convert.ToInt32(DbType.Object), arrayDbType, arrayDbType, false,defaultArray); + } + + } return null; } - protected override string GetComparisonDDLStatements(params TypeSchemaAndName[] objects) + + private Tuple IsArray(Type type) + { + var flag = false; + Type resultType = null; + + if (type.IsArray) + { + flag = true; + resultType = type.GetElementType(); + } + + return new Tuple(flag, resultType); + } + + private Tuple IsCollection(Type type) + { + var flag = false; + Type resultType = null; + var interfaces = type.GetInterfaces(); + + if (interfaces.Any(t => t.Name == "IList")) + flag = true; + + if (interfaces.Any(t => t.Name == "ICollection")) + flag = true; + + if (interfaces.Any(t => t.Name == "IEnumerable")) + flag = true; + + if (type.Name == "Array") + { + flag = true; + resultType = typeof(string); + } + + if (type.Name == "ArrayList") + { + flag = true; + resultType = typeof(string); + } + + //是否是泛型 + if (type.GetGenericArguments().Any()) + { + var first = type.GetGenericArguments().First(); + resultType = first; + } + + return new Tuple(flag, resultType); + } + + protected override string GetComparisonDDLStatements(params TypeAndName[] objects) { Object conn = null; string database = null; @@ -100,11 +169,11 @@ namespace FreeSql.ClickHouse { if (sb.Length > 0) sb.Append("\r\n"); - var tb = obj.tableSchema; + var tb = _commonUtils.GetTableByEntity(obj.entityType); if (tb == null) - throw new Exception(CoreStrings.S_Type_IsNot_Migrable(obj.tableSchema.Type.FullName)); + throw new Exception(CoreStrings.S_Type_IsNot_Migrable(obj.entityType.FullName)); if (tb.Columns.Any() == false) - throw new Exception(CoreStrings.S_Type_IsNot_Migrable_0Attributes(obj.tableSchema.Type.FullName)); + throw new Exception(CoreStrings.S_Type_IsNot_Migrable_0Attributes(obj.entityType.FullName)); var tbname = _commonUtils.SplitTableName(tb.DbName); if (tbname?.Length == 1) tbname = new[] { database, tbname[0] }; @@ -299,7 +368,8 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); //添加列 sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName(tbname[0], tbname[1])) - .Append(" ADD Column ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ") + .Append(" ADD Column ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)) + .Append(" ") .Append(tbcol.Attribute.DbType); if (tbcol.Attribute.IsNullable == false && tbcol.DbDefaultValue != "NULL" && tbcol.Attribute.IsIdentity == false) @@ -483,7 +553,7 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); cmd.CommandText = sql; cmd.CommandType = CommandType.Text; var before = new Aop.CommandBeforeEventArgs(cmd); - this._orm?.Aop.CommandBeforeHandler?.Invoke(this._orm, before); + this._orm?.Aop.CommandBeforeHandler?.Invoke(this._orm, before); Exception afterException = null; try { @@ -496,7 +566,8 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); } finally { - this._orm?.Aop.CommandAfterHandler?.Invoke(this._orm, new Aop.CommandAfterEventArgs(before, afterException, null)); + this._orm?.Aop.CommandAfterHandler?.Invoke(this._orm, + new Aop.CommandAfterEventArgs(before, afterException, null)); } } } @@ -511,12 +582,17 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); { if (isPrimary) { - if (dbType.Contains("Nullable")) - return dbType.Replace("Nullable(", "") + if (dbType.Contains("Nullable")) + { + var res = dbType.Replace("Nullable(", "") .Replace(")", "") .Replace(" NOT NULL", ""); + return res; + } + return dbType; } + return dbType.Replace(" NOT NULL", ""); } @@ -524,6 +600,9 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); string CkIntAdapter(string dbType) { var result = dbType; + if (dbType.Contains("Array")) + return dbType; + if (dbType.ToLower().Contains("int64")) { if (dbType.Contains("Nullable")) @@ -578,13 +657,7 @@ where a.database in ({0}) and a.table in ({1})", tboldname ?? tbname); internal class ClickHouseTableIndex { - public string name - { - get; set; - } - public string expr - { - get; set; - } + public string name { get; set; } + public string expr { get; set; } } } \ No newline at end of file diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs index cc613093..d4a7ea74 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExpression.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; @@ -121,27 +122,51 @@ namespace FreeSql.ClickHouse 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 = objExp?.NodeType == ExpressionType.MemberAccess ? tsc.SetDbParamsReturnOld(null) : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug - tsc.isNotSetMapColumnTmp = true; + //if (argIndex >= callExp.Arguments.Count) break; + //tsc.SetMapColumnTmp(null); + //var args1 = getExp(callExp.Arguments[argIndex]); + //var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + //var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess ? tsc.SetDbParamsReturnOld(null) : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug + //tsc.isNotSetMapColumnTmp = true; var left = objExp == null ? null : getExp(objExp); - tsc.isNotSetMapColumnTmp = false; - tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); - if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); + //tsc.isNotSetMapColumnTmp = false; + //tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + //if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); switch (callExp.Method.Name) { + case "Count": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + return $"(case when {left} is null then 0 else length({left}) end)"; + case "Any": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + return $"(case when {left} is null then 0 else length({left}) end > 0)"; case "Contains": - //判断 in //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 - return $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; + tsc.SetMapColumnTmp(null); + var args1 = getExp(callExp.Arguments[argIndex]); + var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess ? tsc.SetDbParamsReturnOld(null) : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug + tsc.isNotSetMapColumnTmp = true; + left = objExp == null ? null : getExp(objExp); + tsc.isNotSetMapColumnTmp = false; + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + return $"(hasAny({left}, [{args1}]))"; + case "Concat": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + var right2 = getExp(callExp.Arguments[argIndex]); + if (right2.StartsWith("(") || right2.EndsWith(")")) right2 = $"array[{right2.TrimStart('(').TrimEnd(')')}]"; + return $"(arrayConcat({left} || {right2}))"; } } break; @@ -181,7 +206,6 @@ namespace FreeSql.ClickHouse } return null; } - public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc) { if (exp.Expression == null) diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs index bc3bc7c3..36428c07 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseExtensions.cs @@ -2,24 +2,30 @@ using FreeSql.ClickHouse.Curd; using FreeSql.Internal.CommonProvider; using System; +using System.Collections.Generic; using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using FreeSql.DataAnnotations; public static partial class FreeSqlClickHouseGlobalExtensions { - /// /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 /// /// /// /// - public static string FormatClickHouse(this string that, params object[] args) => _clickHouseAdo.Addslashes(that, args); + public static string FormatClickHouse(this string that, params object[] args) => + _clickHouseAdo.Addslashes(that, args); + static FreeSql.ClickHouse.ClickHouseAdo _clickHouseAdo = new FreeSql.ClickHouse.ClickHouseAdo(); /// /// Clickhouse limit by /// - public static ISelect LimitBy(this ISelect that, Expression> selector, int limit, int offset = 0) + public static ISelect LimitBy(this ISelect that, Expression> selector, int limit, + int offset = 0) { if (limit <= 0 && offset <= 0) return that; var s0p = that as ClickHouseSelect; @@ -34,6 +40,7 @@ public static partial class FreeSqlClickHouseGlobalExtensions { s0p._orderby = oldOrderBy; } + return that; } @@ -52,4 +59,36 @@ public static partial class FreeSqlClickHouseGlobalExtensions s0p._sample = $"SAMPLE {k}"; return that; } + + /// + /// 批量快速插入 + /// + /// + /// + /// + public static async Task ExecuteBulkCopyAsync(this IInsert that) where T : class + { + try + { + var insert = that as ClickHouseInsert; + await insert.InternalBulkCopyAsync(); + } + catch (Exception e) + { + throw e; + } + + return 0; + } + + /// + /// 批量快速插入 + /// + /// + /// + /// + public static int ExecuteBulkCopy(this IInsert insert) where T : class + { + return ExecuteBulkCopyAsync(insert).ConfigureAwait(false).GetAwaiter().GetResult(); + } } diff --git a/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs index 126b0704..da3fb3ff 100644 --- a/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs +++ b/Providers/FreeSql.Provider.ClickHouse/ClickHouseUtils.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Globalization; using System.Data; +using System.Text.Json; using ClickHouse.Client.ADO.Parameters; using System.Text.RegularExpressions; @@ -18,7 +19,7 @@ namespace FreeSql.ClickHouse } public override DbParameter AppendParamter(List _params, string parameterName, ColumnInfo col, Type type, object value) - { + { if (value is string str) value = str?.Replace("\t", "\\t") .Replace("\r\n", "\\r\\n") @@ -43,8 +44,10 @@ namespace FreeSql.ClickHouse if (col.DbScale != 0) ret.Scale = col.DbScale; break; } - if (value is bool) - ret.Value = (bool)value ? 1 : 0; + //if (value.GetType().IsArray) + //{ + // ret.DbType = DbType.Object; + //} } _params?.Add(ret); return ret; @@ -145,7 +148,7 @@ namespace FreeSql.ClickHouse } 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[])}"; diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs index 0645397a..9b6eb58d 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -57,18 +57,7 @@ namespace FreeSql.ClickHouse.Curd { before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, null, _params); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); - var data = ToDataTable(); - using (var conn = _orm.Ado.MasterPool.Get()) - { - using (var bulkCopyInterface = new ClickHouseBulkCopy(conn.Value as ClickHouseConnection) - { - DestinationTableName = data.TableName, - BatchSize = _source.Count - }) - { - bulkCopyInterface.WriteToServerAsync(data, default).Wait(); - } - } + InternalBulkCopyAsync().ConfigureAwait(false).GetAwaiter().GetResult(); return affrows; } catch (Exception ex) @@ -85,6 +74,24 @@ namespace FreeSql.ClickHouse.Curd return base.RawExecuteAffrows(); } + internal async Task InternalBulkCopyAsync() + { + var data = ToDataTable(); + using (var conn = _orm.Ado.MasterPool.Get()) + { + using (var bulkCopyInterface = new ClickHouseBulkCopy(conn.Value as ClickHouseConnection) + { + DestinationTableName = data.TableName, + BatchSize = _source.Count + }) + { + await bulkCopyInterface.WriteToServerAsync(data, default); + } + } + return 0; + + } + private IDictionary GetValue(T u, System.Reflection.PropertyInfo[] columns) { try