Skip to content

Commit f543955

Browse files
committed
Add Csv query output, improve json output
1 parent f13712c commit f543955

File tree

20 files changed

+636
-54
lines changed

20 files changed

+636
-54
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ _Pvt_Extensions
239239
/artifacts
240240
/Tools
241241
*.GhostDoc.xml
242-
*.csv
243242
coverage.xml
244243
coverage.opencover.xml
245244
*.received.txt

FluentCommand.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Generators.Te
3535
EndProject
3636
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Performance", "test\FluentCommand.Performance\FluentCommand.Performance.csproj", "{A5C1646D-4508-46C4-9C5B-17D0654AFEE9}"
3737
EndProject
38-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCommand.Caching", "src\FluentCommand.Caching\FluentCommand.Caching.csproj", "{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}"
38+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Caching", "src\FluentCommand.Caching\FluentCommand.Caching.csproj", "{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}"
39+
EndProject
40+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCommand.Csv", "src\FluentCommand.Csv\FluentCommand.Csv.csproj", "{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}"
3941
EndProject
4042
Global
4143
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -91,6 +93,10 @@ Global
9193
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
9294
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
9395
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Release|Any CPU.Build.0 = Release|Any CPU
96+
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
97+
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Debug|Any CPU.Build.0 = Debug|Any CPU
98+
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Release|Any CPU.ActiveCfg = Release|Any CPU
99+
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Release|Any CPU.Build.0 = Release|Any CPU
94100
EndGlobalSection
95101
GlobalSection(SolutionProperties) = preSolution
96102
HideSolutionNode = FALSE

src/Directory.Build.props

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@
3232
<PropertyGroup>
3333
<MinVerTagPrefix>v</MinVerTagPrefix>
3434
</PropertyGroup>
35-
35+
3636
<ItemGroup>
3737
<PackageReference Include="AssemblyMetadata.Generators" Version="2.0.0" PrivateAssets="All" />
38-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
39-
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="All" />
38+
<PackageReference Include="MinVer" Version="5.0.0" PrivateAssets="All" />
4039
</ItemGroup>
4140

4241
<ItemGroup>

src/FluentCommand.Caching/FluentCommand.Caching.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ItemGroup>
88
<PackageReference Include="MessagePack" Version="2.5.140" />
99
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
10-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
10+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
1111
</ItemGroup>
1212

1313
<ItemGroup>
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
using System.Buffers;
2+
using System.Data;
3+
using System.Data.Common;
4+
using System.Globalization;
5+
using System.Text;
6+
7+
using CsvHelper;
8+
using CsvHelper.Configuration;
9+
10+
using Microsoft.IO;
11+
12+
namespace FluentCommand;
13+
14+
public static class CsvCommandExtensions
15+
{
16+
private static readonly RecyclableMemoryStreamManager _memoryStreamManager = new();
17+
18+
/// <summary>
19+
/// Executes the query and returns a CSV string from data set returned by the query.
20+
/// </summary>
21+
/// <param name="dataCommand">The data command.</param>
22+
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
23+
/// <returns>
24+
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
25+
/// </returns>
26+
public static string QueryCsv(this IDataCommand dataCommand, CsvConfiguration csvConfiguration = default)
27+
{
28+
using var stream = _memoryStreamManager.GetStream();
29+
30+
QueryCsv(dataCommand, stream, csvConfiguration);
31+
32+
var bytes = stream.GetReadOnlySequence();
33+
34+
#if NETSTANDARD2_1_OR_GREATER
35+
return Encoding.UTF8.GetString(bytes);
36+
#else
37+
return Encoding.UTF8.GetString(bytes.ToArray());
38+
#endif
39+
}
40+
41+
/// <summary>
42+
/// Executes the query and writes the CSV data to the specified <paramref name="stream"/>.
43+
/// </summary>
44+
/// <param name="dataCommand">The data command.</param>
45+
/// <param name="stream">The stream writer.</param>
46+
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
47+
public static void QueryCsv(this IDataCommand dataCommand, Stream stream, CsvConfiguration csvConfiguration = default)
48+
{
49+
if (csvConfiguration == null)
50+
csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = true };
51+
52+
using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
53+
using var csvWriter = new CsvWriter(streamWriter, csvConfiguration, true);
54+
55+
dataCommand.Read(reader => WriteData(reader, csvWriter), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
56+
57+
csvWriter.Flush();
58+
streamWriter.Flush();
59+
}
60+
61+
62+
/// <summary>
63+
/// Executes the query and returns a CSV string from data set returned by the query asynchronously.
64+
/// </summary>
65+
/// <param name="dataCommand">The data command.</param>
66+
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
67+
/// <param name="cancellationToken">The cancellation token.</param>
68+
/// <returns>
69+
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
70+
/// </returns>
71+
public static async Task<string> QueryCsvAsync(this IDataCommand dataCommand, CsvConfiguration csvConfiguration = default, CancellationToken cancellationToken = default)
72+
{
73+
using var stream = _memoryStreamManager.GetStream();
74+
75+
await QueryCsvAsync(dataCommand, stream, csvConfiguration, cancellationToken);
76+
77+
var bytes = stream.GetReadOnlySequence();
78+
#if NETSTANDARD2_1_OR_GREATER
79+
return Encoding.UTF8.GetString(bytes);
80+
#else
81+
return Encoding.UTF8.GetString(bytes.ToArray());
82+
#endif
83+
}
84+
85+
/// <summary>
86+
/// Executes the query and writes the CSV data to the specified <paramref name="stream"/>.
87+
/// </summary>
88+
/// <param name="dataCommand">The data command.</param>
89+
/// <param name="stream">The stream writer.</param>
90+
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
91+
/// <param name="cancellationToken">The cancellation token.</param>
92+
/// <returns>
93+
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
94+
/// </returns>
95+
public static async Task QueryCsvAsync(this IDataCommand dataCommand, Stream stream, CsvConfiguration csvConfiguration = default, CancellationToken cancellationToken = default)
96+
{
97+
if (csvConfiguration == null)
98+
csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = true };
99+
100+
using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
101+
await using var csvWriter = new CsvWriter(streamWriter, csvConfiguration, true);
102+
103+
await dataCommand.ReadAsync(async (reader, token) =>
104+
{
105+
if (reader is DbDataReader dataReader)
106+
await WriteDataAsync(dataReader, csvWriter, token);
107+
else
108+
WriteData(reader, csvWriter);
109+
110+
}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancellationToken);
111+
112+
await csvWriter.FlushAsync();
113+
await streamWriter.FlushAsync();
114+
}
115+
116+
117+
private static void WriteData(IDataReader reader, CsvWriter writer)
118+
{
119+
// if config says to include header, default false
120+
var wroteHeader = !writer.Configuration.HasHeaderRecord;
121+
122+
while (reader.Read())
123+
{
124+
if (!wroteHeader)
125+
{
126+
WriteHeader(reader, writer);
127+
wroteHeader = true;
128+
}
129+
130+
WriteRow(reader, writer);
131+
}
132+
}
133+
134+
private static async Task WriteDataAsync(DbDataReader reader, CsvWriter writer, CancellationToken cancellationToken = default)
135+
{
136+
// if config says to include header, default false
137+
var wroteHeader = !writer.Configuration.HasHeaderRecord;
138+
139+
while (await reader.ReadAsync(cancellationToken))
140+
{
141+
if (!wroteHeader)
142+
{
143+
WriteHeader(reader, writer);
144+
wroteHeader = true;
145+
}
146+
147+
WriteRow(reader, writer);
148+
}
149+
}
150+
151+
private static void WriteHeader(IDataReader reader, CsvWriter writer)
152+
{
153+
for (int index = 0; index < reader.FieldCount; index++)
154+
{
155+
var name = reader.GetName(index);
156+
writer.WriteField(name);
157+
}
158+
writer.NextRecord();
159+
}
160+
161+
private static void WriteRow(IDataReader reader, CsvWriter writer)
162+
{
163+
for (int index = 0; index < reader.FieldCount; index++)
164+
{
165+
WriteValue(reader, writer, index);
166+
}
167+
writer.NextRecord();
168+
}
169+
170+
private static void WriteValue(IDataReader reader, CsvWriter writer, int index)
171+
{
172+
if (reader.IsDBNull(index))
173+
{
174+
writer.WriteField(string.Empty);
175+
return;
176+
}
177+
178+
var type = reader.GetFieldType(index);
179+
180+
if (type == typeof(string))
181+
{
182+
var value = reader.GetString(index);
183+
writer.WriteField(value);
184+
return;
185+
}
186+
187+
if (type == typeof(bool))
188+
{
189+
var value = reader.GetBoolean(index);
190+
writer.WriteField(value);
191+
return;
192+
}
193+
194+
if (type == typeof(byte))
195+
{
196+
var value = reader.GetByte(index);
197+
writer.WriteField(value);
198+
return;
199+
}
200+
201+
if (type == typeof(short))
202+
{
203+
var value = reader.GetInt16(index);
204+
writer.WriteField(value);
205+
return;
206+
}
207+
208+
if (type == typeof(int))
209+
{
210+
var value = reader.GetInt32(index);
211+
writer.WriteField(value);
212+
return;
213+
}
214+
215+
if (type == typeof(long))
216+
{
217+
var value = reader.GetInt64(index);
218+
writer.WriteField(value);
219+
return;
220+
}
221+
222+
if (type == typeof(float))
223+
{
224+
var value = reader.GetFloat(index);
225+
writer.WriteField(value);
226+
return;
227+
}
228+
229+
if (type == typeof(double))
230+
{
231+
var value = reader.GetDouble(index);
232+
writer.WriteField(value);
233+
return;
234+
}
235+
236+
if (type == typeof(decimal))
237+
{
238+
var value = reader.GetDecimal(index);
239+
writer.WriteField(value);
240+
return;
241+
}
242+
243+
if (type == typeof(TimeSpan))
244+
{
245+
var value = reader.GetDateTime(index);
246+
writer.WriteField(value);
247+
return;
248+
}
249+
250+
if (type == typeof(DateTime))
251+
{
252+
var value = reader.GetDateTime(index);
253+
writer.WriteField(value);
254+
return;
255+
}
256+
257+
if (type == typeof(DateTimeOffset))
258+
{
259+
var value = reader.GetValue(index);
260+
if (value is DateTimeOffset offset)
261+
{
262+
writer.WriteField(offset);
263+
return;
264+
}
265+
266+
var date = reader.GetDateTime(index);
267+
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
268+
269+
offset = new DateTimeOffset(date, TimeSpan.Zero);
270+
271+
writer.WriteField(offset);
272+
return;
273+
}
274+
275+
if (type == typeof(Guid))
276+
{
277+
var value = reader.GetGuid(index);
278+
writer.WriteField(value);
279+
return;
280+
}
281+
282+
// fallback
283+
var v = reader.GetValue(index);
284+
writer.WriteField(v.ToString());
285+
}
286+
287+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
5+
<RootNamespace>FluentCommand</RootNamespace>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="CsvHelper" Version="31.0.2" />
10+
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\FluentCommand\FluentCommand.csproj" />
15+
</ItemGroup>
16+
17+
18+
</Project>

src/FluentCommand.Generators/FluentCommand.Generators.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</PropertyGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
20+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.3.1]" PrivateAssets="all" />
2121
</ItemGroup>
2222

2323
</Project>

src/FluentCommand.Json/FluentCommand.Json.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="System.Text.Json" Version="8.0.1" />
12+
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
13+
<PackageReference Include="System.Text.Json" Version="8.0.3" />
1314
</ItemGroup>
1415

1516
</Project>

0 commit comments

Comments
 (0)