diff --git a/src/Carbunql/Building/IEnumerableExtension.cs b/src/Carbunql/Building/IEnumerableExtension.cs new file mode 100644 index 00000000..d3b0e9b7 --- /dev/null +++ b/src/Carbunql/Building/IEnumerableExtension.cs @@ -0,0 +1,49 @@ +using Carbunql.Analysis.Parser; +using Carbunql.Clauses; +using Carbunql.Values; + +namespace Carbunql.Building; + +public static class IEnumerableExtension +{ + public static ValuesQuery ToValuesQuery(this IEnumerable source) + { + return ToValuesQuery(source, DbmsConfiguration.PlaceholderIdentifier); + } + + public static ValuesQuery ToValuesQuery(this IEnumerable source, string placeholderIndentifer) + { + var vq = new ValuesQuery(); + var r = 0; + foreach (var row in source) + { + var lst = new List(); + var c = 0; + foreach (var column in typeof(T).GetProperties().Where(x => x.CanRead && x.CanWrite).Select(x => x.GetValue(row))) + { + var name = $"r{r}c{c}"; + var v = placeholderIndentifer + name; + lst.Add(ValueParser.Parse(v)); + vq.AddParameter(name, column); + c++; + } + vq.Rows.Add(new ValueCollection(lst)); + r++; + } + return vq; + } + + public static SelectQuery ToSelectQuery(this IEnumerable source) + { + return source.ToSelectQuery(DbmsConfiguration.PlaceholderIdentifier, x => x.ToLowerSnakeCase()); + } + + public static SelectQuery ToSelectQuery(this IEnumerable source, string placeholderIndentifer, Func propertyNameConverter) + { + var columns = typeof(T).GetProperties().Where(x => x.CanRead && x.CanWrite).Select(x => propertyNameConverter(x.Name)).ToList(); + var vq = source.ToValuesQuery(placeholderIndentifer); + var sq = vq.ToSelectQuery(columns); + + return sq; + } +} diff --git a/src/Carbunql/Carbunql.csproj b/src/Carbunql/Carbunql.csproj index 9b54007d..66ede7e5 100644 --- a/src/Carbunql/Carbunql.csproj +++ b/src/Carbunql/Carbunql.csproj @@ -7,7 +7,7 @@ mk3008net Carbunql provides query parsing and building functionality. - 0.7.4 + 0.7.5 mk3008net https://github.com/mk3008/Carbunql README.md diff --git a/src/Carbunql/IQueryCommandable.cs b/src/Carbunql/IQueryCommandable.cs index be1f04fb..d08cb460 100644 --- a/src/Carbunql/IQueryCommandable.cs +++ b/src/Carbunql/IQueryCommandable.cs @@ -89,12 +89,12 @@ public static QueryCommand ToOneLineCommand(this IQueryCommandable source) return new QueryCommand(source.GetTokens().ToText(), source.GetParameters()); } - public static string ToText(this IQueryCommandable source) + public static string ToText(this IQueryCommandable source, bool exportParameterInfo = true) { var cmd = source.ToCommand(); var text = cmd.CommandText; - if (!cmd.Parameters.Any()) return text; + if (!cmd.Parameters.Any() || !exportParameterInfo) return text; var head = GetParameterText(cmd); if (string.IsNullOrEmpty(head)) return text; diff --git a/src/Carbunql/ValuesQuery.cs b/src/Carbunql/ValuesQuery.cs index ea50ed0e..d3928af8 100644 --- a/src/Carbunql/ValuesQuery.cs +++ b/src/Carbunql/ValuesQuery.cs @@ -30,6 +30,8 @@ public ValuesQuery(string query) LimitClause = q.LimitClause; } + + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(IEnumerable matrix) { foreach (var row in matrix) @@ -38,20 +40,21 @@ public ValuesQuery(IEnumerable matrix) foreach (var column in row.GetType().GetProperties().Where(x => x.CanRead && x.CanWrite).Select(x => x.GetValue(row))) { - if (column == null) + if (column == null || column.ToString() == null) { lst.Add(new LiteralValue("null")); } else { var v = $"\"{column}\""; - lst.Add(ValueParser.Parse(v)); + lst.Add(ValueParser.Parse(column.ToString()!)); } } Rows.Add(new ValueCollection(lst)); } } + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(IEnumerable matrix, string placeholderIndentifer) { var r = 0; @@ -73,6 +76,7 @@ public ValuesQuery(IEnumerable matrix, string placeholderIndentifer) } } + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(string[,] matrix) { for (int row = 0; row < matrix.GetLength(0); row++) @@ -88,6 +92,7 @@ public ValuesQuery(string[,] matrix) } } + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(IEnumerable> matrix) { foreach (var row in matrix) @@ -102,6 +107,7 @@ public ValuesQuery(IEnumerable> matrix) } } + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(string[,] matrix, string placeholderIndentifer) { for (int row = 0; row < matrix.GetLength(0); row++) @@ -119,6 +125,7 @@ public ValuesQuery(string[,] matrix, string placeholderIndentifer) } } + [Obsolete("This feature has been deprecated. Consider building it outside of class.")] public ValuesQuery(IEnumerable> matrix, string placeholderIndentifer) { var r = 0; diff --git a/test/Carbunql.Building.Test/IEnumerableTest.cs b/test/Carbunql.Building.Test/IEnumerableTest.cs new file mode 100644 index 00000000..f4f8e69d --- /dev/null +++ b/test/Carbunql.Building.Test/IEnumerableTest.cs @@ -0,0 +1,148 @@ +using Xunit.Abstractions; + +namespace Carbunql.Building.Test; + +public class IEnumerableTest +{ + private readonly QueryCommandMonitor Monitor; + + public IEnumerableTest(ITestOutputHelper output) + { + Monitor = new QueryCommandMonitor(output); + Output = output; + } + + private ITestOutputHelper Output { get; set; } + + [Fact] + public void ToValuesQuery() + { + var results = new List() + { + new (){ ResultId = 1 , ResultText = "a", ResultValue = 10, ResultDate = new DateTime(2000,1,1), ResultBool= true }, + new (){ ResultId = 2 , ResultText = "b", ResultValue = 20, ResultDate = new DateTime(2010,10,10), ResultBool= false }, + new (){ ResultId = 3 , ResultText = null, ResultValue = null, ResultDate = null, ResultBool= null}, + }; + + var q = results.ToValuesQuery(); + + var actual = q.ToText(true); + Output.WriteLine(actual); + + var expect = @"/* + r0c0 = 1 + r0c1 = 'a' + r0c2 = 10 + r0c3 = 2000/01/01 0:00:00 + r0c4 = True + r1c0 = 2 + r1c1 = 'b' + r1c2 = 20 + r1c3 = 2010/10/10 0:00:00 + r1c4 = False + r2c0 = 3 + r2c1 is NULL + r2c2 is NULL + r2c3 is NULL + r2c4 is NULL +*/ +VALUES + (:r0c0, :r0c1, :r0c2, :r0c3, :r0c4), + (:r1c0, :r1c1, :r1c2, :r1c3, :r1c4), + (:r2c0, :r2c1, :r2c2, :r2c3, :r2c4)"; + + Assert.Equal(expect, actual, true, true, true); + } + + [Fact] + public void ToSelectQuery() + { + var results = new List() + { + new (){ ResultId = 1 , ResultText = "a", ResultValue = 10, ResultDate = new DateTime(2000,1,1), ResultBool= true }, + new (){ ResultId = 2 , ResultText = "b", ResultValue = 20, ResultDate = new DateTime(2010,10,10), ResultBool= false }, + new (){ ResultId = 3 , ResultText = null, ResultValue = null, ResultDate = null, ResultBool= null}, + }; + + var q = results.ToSelectQuery(); + + var actual = q.ToText(true); + Output.WriteLine(actual); + + var expect = @"/* + r0c0 = 1 + r0c1 = 'a' + r0c2 = 10 + r0c3 = 2000/01/01 0:00:00 + r0c4 = True + r1c0 = 2 + r1c1 = 'b' + r1c2 = 20 + r1c3 = 2010/10/10 0:00:00 + r1c4 = False + r2c0 = 3 + r2c1 is NULL + r2c2 is NULL + r2c3 is NULL + r2c4 is NULL +*/ +SELECT + v.result_id, + v.result_text, + v.result_value, + v.result_date, + v.result_bool +FROM + ( + VALUES + (:r0c0, :r0c1, :r0c2, :r0c3, :r0c4), + (:r1c0, :r1c1, :r1c2, :r1c3, :r1c4), + (:r2c0, :r2c1, :r2c2, :r2c3, :r2c4) + ) AS v ( + result_id, result_text, result_value, result_date, result_bool + )"; + + Assert.Equal(expect, actual, true, true, true); + } + + [Fact] + public void ToSelectQuery_50k() + { + var results = GenerateDummyResults(50000); + + var q = results.ToSelectQuery(); + + var actual = q.ToText(false); + Output.WriteLine(actual); + } + + private static List GenerateDummyResults(int count) + { + var dummyResults = new List(); + + for (int i = 0; i < count; i++) + { + var result = new ApiResult + { + ResultId = i + 1, + ResultText = "dummy", + ResultValue = i * 10, + ResultDate = DateTime.Now.AddDays(i), + ResultBool = i % 2 == 0 + }; + + dummyResults.Add(result); + } + + return dummyResults; + } + + public class ApiResult + { + public int ResultId { get; set; } + public string? ResultText { get; set; } + public long? ResultValue { get; set; } + public DateTime? ResultDate { get; set; } + public bool? ResultBool { get; set; } + } +}