Skip to content

Commit

Permalink
Allow populating array columns from any data source
Browse files Browse the repository at this point in the history
  • Loading branch information
YuriyIvon committed Nov 3, 2024
1 parent 0e1c9e6 commit 28bf46b
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using DatabaseBenchmark.Common;
using DatabaseBenchmark.DataSources.Interfaces;
using DatabaseBenchmark.Model;
using System.Collections;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace DatabaseBenchmark.DataSources.Decorators
{
Expand Down Expand Up @@ -31,14 +34,12 @@ private static object ConvertValue(Column column, object value, IFormatProvider
}
else if (column.Array)
{
if (value is IEnumerable<object> valueCollection)
return value switch
{
return valueCollection.Select(v => ConvertSingleValue(column, v, formatProvider)).ToArray();
}
else
{
throw new InputArgumentException($"A non-array value received for an array column {column.Name}");
}
string jsonString => NormalizeArray(column, ParseJsonArray(jsonString), formatProvider),
IEnumerable valueCollection => NormalizeArray(column, valueCollection, formatProvider),
_ => throw new InputArgumentException($"The value received for an array column \"{column.Name}\" is neither an array nor a JSON array string")
};
}
else
{
Expand Down Expand Up @@ -159,5 +160,39 @@ private static object HandleFormatException(Func<object> parseFunction, object v

private static Exception CreateConvertException(object value, Type targetType) =>
new InputArgumentException($"A value \"{value}\" of type \"{value.GetType()}\" can't be converted to \"{targetType}\"");

private static object NormalizeArray(Column column, IEnumerable valueCollection, IFormatProvider formatProvider) =>
valueCollection.Cast<object>().Select(v => ConvertSingleValue(column, v, formatProvider)).ToArray();

private static object GetJsonNodeValue(JsonNode node) =>
node switch
{
JsonValue value => value.GetValueKind() switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Number => value.GetValue<double>(),
_ => value.ToString()
},
_ => throw new InputArgumentException("An array column can store only primitive values")
};

private static IEnumerable<object> ParseJsonArray(string jsonString)
{
try
{
if (JsonNode.Parse(jsonString) is not JsonArray jsonArray)
{
throw new InputArgumentException($"The string \"{jsonString}\" is not a valid JSON array definition");
}

return jsonArray.Select(GetJsonNodeValue).ToArray();
}
catch
{
throw new InputArgumentException($"The string \"{jsonString}\" is not a valid JSON array definition");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,36 @@ public class DataSourceTypedColumnsDecoratorTests
[ColumnType.Guid, "abc"]
];

public static IEnumerable<object[]> ValidArraySamples =>
[
[ColumnType.Boolean, new List<bool> { true, false }, new bool[] { true, false }],
[ColumnType.Integer, new List<int> { 1, 2, 3 }, new int[] { 1, 2, 3 }],
[ColumnType.Long, new List<long> { 1, 2, 3 }, new long[] { 1, 2, 3 }],
[ColumnType.Double, new List<double> { 1.1, 2.1, 3.1 }, new double[] { 1.1, 2.1, 3.1 }],
[ColumnType.DateTime, new List<DateTime> { new (2024, 10, 2), new (2024, 11, 2) }, new DateTime[] { new (2024, 10, 2), new (2024, 11, 2) }],
[ColumnType.Guid, new List<Guid> { new ("271bf677-4bf5-4fc7-b6f1-93bd2bd8ebb5"), new ("91a4388d-0deb-4b22-a8f3-3d8be3f0193b") }, new Guid[] { new("271bf677-4bf5-4fc7-b6f1-93bd2bd8ebb5"), new("91a4388d-0deb-4b22-a8f3-3d8be3f0193b") }],
[ColumnType.String, new List<string> { "one", "two", "three" }, new string[] { "one", "two", "three" }],
[ColumnType.Boolean, "[true, false]", new bool[] { true, false }],
[ColumnType.Integer, "[1, 2, 3]", new int[] { 1, 2, 3 }],
[ColumnType.Long, "[1, 2, 3]", new long[] { 1, 2, 3 }],
[ColumnType.Double, "[1.1, 2.1, 3.1]", new double[] { 1.1, 2.1, 3.1 }],
[ColumnType.DateTime, "[\"2024-10-02\", \"2024-11-02\"]", new DateTime[] { new (2024, 10, 2), new (2024, 11, 2) }],
[ColumnType.Guid, "[\"271bf677-4bf5-4fc7-b6f1-93bd2bd8ebb5\", \"91a4388d-0deb-4b22-a8f3-3d8be3f0193b\"]", new Guid[] { new("271bf677-4bf5-4fc7-b6f1-93bd2bd8ebb5"), new("91a4388d-0deb-4b22-a8f3-3d8be3f0193b") }],
[ColumnType.String, "[\"one\", \"two\", \"three\"]", new string[] { "one", "two", "three" }]
];

public static IEnumerable<object[]> InvalidArraySamples =>
[
[ColumnType.String, "1234"],
[ColumnType.Integer, "1234"],
[ColumnType.String, 123],
[ColumnType.Integer, 123],
[ColumnType.String, new object()],
[ColumnType.String, "{\"one\": \"two\"}"],
[ColumnType.String, "[[1, 2], [1, 2, 3]]"],
[ColumnType.String, "[{}, {}]"],
];

[Theory]
[MemberData(nameof(CompatibleTypeSamples))]
public void ReadCompatibleValue(ColumnType columnType, object sourceValue, object targetValue)
Expand Down Expand Up @@ -107,6 +137,38 @@ public void ReadIncompatibleValue(ColumnType columnType, object sourceValue)
Assert.Throws<InputArgumentException>(() => dataSource.GetValue(columns[0].Name));
}

[Theory]
[MemberData(nameof(ValidArraySamples))]
public void ReadValidArrayValues(ColumnType columnType, object sourceValue, object targetValue)
{
var columns = new Column[]
{
new() { Name = "Dummy", Type = columnType, Array = true }
};

IDataSource dataSource = new TestDataSource(sourceValue);
dataSource = new DataSourceTypedColumnsDecorator(dataSource, columns, CultureInfo.InvariantCulture);

var value = dataSource.GetValue(columns[0].Name);

Assert.Equal(targetValue, value);
}

[Theory]
[MemberData(nameof(InvalidArraySamples))]
public void ReadInvalidArrayValues(ColumnType columnType, object sourceValue)
{
var columns = new Column[]
{
new() { Name = "Dummy", Type = columnType, Array = true }
};

IDataSource dataSource = new TestDataSource(sourceValue);
dataSource = new DataSourceTypedColumnsDecorator(dataSource, columns, CultureInfo.InvariantCulture);

Assert.Throws<InputArgumentException>(() => dataSource.GetValue(columns[0].Name));
}

private class TestDataSource : IDataSource
{
private readonly object _value;
Expand Down

0 comments on commit 28bf46b

Please sign in to comment.