Skip to content

Commit

Permalink
Add Csv query output, improve json output
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Mar 19, 2024
1 parent f13712c commit f543955
Show file tree
Hide file tree
Showing 20 changed files with 636 additions and 54 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ _Pvt_Extensions
/artifacts
/Tools
*.GhostDoc.xml
*.csv
coverage.xml
coverage.opencover.xml
*.received.txt
Expand Down
8 changes: 7 additions & 1 deletion FluentCommand.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Generators.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Performance", "test\FluentCommand.Performance\FluentCommand.Performance.csproj", "{A5C1646D-4508-46C4-9C5B-17D0654AFEE9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCommand.Caching", "src\FluentCommand.Caching\FluentCommand.Caching.csproj", "{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Caching", "src\FluentCommand.Caching\FluentCommand.Caching.csproj", "{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCommand.Csv", "src\FluentCommand.Csv\FluentCommand.Csv.csproj", "{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -91,6 +93,10 @@ Global
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4565EA0D-C7E4-4327-B78D-C5B19C76AC8A}.Release|Any CPU.Build.0 = Release|Any CPU
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE8CBF1C-7231-4D26-A2DB-EDB0E7F29871}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
5 changes: 2 additions & 3 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@
<PropertyGroup>
<MinVerTagPrefix>v</MinVerTagPrefix>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AssemblyMetadata.Generators" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="All" />
<PackageReference Include="MinVer" Version="5.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCommand.Caching/FluentCommand.Caching.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="MessagePack" Version="2.5.140" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
287 changes: 287 additions & 0 deletions src/FluentCommand.Csv/CsvCommandExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
using System.Buffers;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Text;

using CsvHelper;
using CsvHelper.Configuration;

using Microsoft.IO;

namespace FluentCommand;

public static class CsvCommandExtensions
{
private static readonly RecyclableMemoryStreamManager _memoryStreamManager = new();

/// <summary>
/// Executes the query and returns a CSV string from data set returned by the query.
/// </summary>
/// <param name="dataCommand">The data command.</param>
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
/// <returns>
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
/// </returns>
public static string QueryCsv(this IDataCommand dataCommand, CsvConfiguration csvConfiguration = default)
{
using var stream = _memoryStreamManager.GetStream();

QueryCsv(dataCommand, stream, csvConfiguration);

var bytes = stream.GetReadOnlySequence();

#if NETSTANDARD2_1_OR_GREATER
return Encoding.UTF8.GetString(bytes);
#else
return Encoding.UTF8.GetString(bytes.ToArray());
#endif
}

/// <summary>
/// Executes the query and writes the CSV data to the specified <paramref name="stream"/>.
/// </summary>
/// <param name="dataCommand">The data command.</param>
/// <param name="stream">The stream writer.</param>
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
public static void QueryCsv(this IDataCommand dataCommand, Stream stream, CsvConfiguration csvConfiguration = default)
{
if (csvConfiguration == null)
csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = true };

using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
using var csvWriter = new CsvWriter(streamWriter, csvConfiguration, true);

dataCommand.Read(reader => WriteData(reader, csvWriter), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);

csvWriter.Flush();
streamWriter.Flush();
}


/// <summary>
/// Executes the query and returns a CSV string from data set returned by the query asynchronously.
/// </summary>
/// <param name="dataCommand">The data command.</param>
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
/// </returns>
public static async Task<string> QueryCsvAsync(this IDataCommand dataCommand, CsvConfiguration csvConfiguration = default, CancellationToken cancellationToken = default)
{
using var stream = _memoryStreamManager.GetStream();

await QueryCsvAsync(dataCommand, stream, csvConfiguration, cancellationToken);

var bytes = stream.GetReadOnlySequence();
#if NETSTANDARD2_1_OR_GREATER
return Encoding.UTF8.GetString(bytes);
#else
return Encoding.UTF8.GetString(bytes.ToArray());
#endif
}

/// <summary>
/// Executes the query and writes the CSV data to the specified <paramref name="stream"/>.
/// </summary>
/// <param name="dataCommand">The data command.</param>
/// <param name="stream">The stream writer.</param>
/// <param name="csvConfiguration">The configuration used for the CSV writer</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A CSV string representing the <see cref="IDataReader" /> result of the command.
/// </returns>
public static async Task QueryCsvAsync(this IDataCommand dataCommand, Stream stream, CsvConfiguration csvConfiguration = default, CancellationToken cancellationToken = default)
{
if (csvConfiguration == null)
csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = true };

using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
await using var csvWriter = new CsvWriter(streamWriter, csvConfiguration, true);

await dataCommand.ReadAsync(async (reader, token) =>
{
if (reader is DbDataReader dataReader)
await WriteDataAsync(dataReader, csvWriter, token);
else
WriteData(reader, csvWriter);

}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancellationToken);

await csvWriter.FlushAsync();
await streamWriter.FlushAsync();
}


private static void WriteData(IDataReader reader, CsvWriter writer)
{
// if config says to include header, default false
var wroteHeader = !writer.Configuration.HasHeaderRecord;

while (reader.Read())
{
if (!wroteHeader)
{
WriteHeader(reader, writer);
wroteHeader = true;
}

WriteRow(reader, writer);
}
}

private static async Task WriteDataAsync(DbDataReader reader, CsvWriter writer, CancellationToken cancellationToken = default)
{
// if config says to include header, default false
var wroteHeader = !writer.Configuration.HasHeaderRecord;

while (await reader.ReadAsync(cancellationToken))
{
if (!wroteHeader)
{
WriteHeader(reader, writer);
wroteHeader = true;
}

WriteRow(reader, writer);
}
}

private static void WriteHeader(IDataReader reader, CsvWriter writer)
{
for (int index = 0; index < reader.FieldCount; index++)
{
var name = reader.GetName(index);
writer.WriteField(name);
}
writer.NextRecord();
}

private static void WriteRow(IDataReader reader, CsvWriter writer)
{
for (int index = 0; index < reader.FieldCount; index++)
{
WriteValue(reader, writer, index);
}
writer.NextRecord();
}

private static void WriteValue(IDataReader reader, CsvWriter writer, int index)
{
if (reader.IsDBNull(index))
{
writer.WriteField(string.Empty);
return;
}

var type = reader.GetFieldType(index);

if (type == typeof(string))
{
var value = reader.GetString(index);
writer.WriteField(value);
return;
}

if (type == typeof(bool))
{
var value = reader.GetBoolean(index);
writer.WriteField(value);
return;
}

if (type == typeof(byte))
{
var value = reader.GetByte(index);
writer.WriteField(value);
return;
}

if (type == typeof(short))
{
var value = reader.GetInt16(index);
writer.WriteField(value);
return;
}

if (type == typeof(int))
{
var value = reader.GetInt32(index);
writer.WriteField(value);
return;
}

if (type == typeof(long))
{
var value = reader.GetInt64(index);
writer.WriteField(value);
return;
}

if (type == typeof(float))
{
var value = reader.GetFloat(index);
writer.WriteField(value);
return;
}

if (type == typeof(double))
{
var value = reader.GetDouble(index);
writer.WriteField(value);
return;
}

if (type == typeof(decimal))
{
var value = reader.GetDecimal(index);
writer.WriteField(value);
return;
}

if (type == typeof(TimeSpan))
{
var value = reader.GetDateTime(index);
writer.WriteField(value);
return;
}

if (type == typeof(DateTime))
{
var value = reader.GetDateTime(index);
writer.WriteField(value);
return;
}

if (type == typeof(DateTimeOffset))
{
var value = reader.GetValue(index);
if (value is DateTimeOffset offset)
{
writer.WriteField(offset);
return;
}

var date = reader.GetDateTime(index);
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);

offset = new DateTimeOffset(date, TimeSpan.Zero);

writer.WriteField(offset);
return;
}

if (type == typeof(Guid))
{
var value = reader.GetGuid(index);
writer.WriteField(value);
return;
}

// fallback
var v = reader.GetValue(index);
writer.WriteField(v.ToString());
}

}
18 changes: 18 additions & 0 deletions src/FluentCommand.Csv/FluentCommand.Csv.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RootNamespace>FluentCommand</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" Version="31.0.2" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FluentCommand\FluentCommand.csproj" />
</ItemGroup>


</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.3.1]" PrivateAssets="all" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion src/FluentCommand.Json/FluentCommand.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>

</Project>
Loading

0 comments on commit f543955

Please sign in to comment.