Skip to content

Commit

Permalink
#121. Partially fixed LowCardinality<Nullable<Something>> columns (at…
Browse files Browse the repository at this point in the history
… least is does not crash).
  • Loading branch information
Andrey Zakharov committed Oct 10, 2023
1 parent a7d4291 commit bef6f54
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 4 deletions.
12 changes: 9 additions & 3 deletions ClickHouse.Ado/Impl/ColumnTypes/LowCardinalityColumnType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ public override async Task Write(ProtocolFormatter formatter, int rows, Cancella
await formatter.WriteBytes(BitConverter.GetBytes(1L), cToken);
await formatter.WriteBytes(BitConverter.GetBytes(1538L), cToken);
await formatter.WriteBytes(BitConverter.GetBytes((long)rows), cToken);
await InnerType.Write(formatter, rows, cToken);
if (InnerType is NullableColumnType nct)
await nct.WriteOnlyInner(formatter, rows, cToken);
else
await InnerType.Write(formatter, rows, cToken);
await formatter.WriteBytes(BitConverter.GetBytes((long)rows), cToken);
for (var i = 0; i < rows; i++)
await formatter.WriteBytes(BitConverter.GetBytes(i), cToken);
Expand All @@ -43,7 +46,10 @@ internal override async Task Read(ProtocolFormatter formatter, int rows, Cancell
if (((keyLength >> 8) & 0xff) != 6)
throw new NotSupportedException("Invalid LowCardinality key flags");
var keyCount = BitConverter.ToInt64(await formatter.ReadBytes(8, -1, cToken), 0);
await InnerType.Read(formatter, (int)keyCount, cToken);
if (InnerType is NullableColumnType nct)
await nct.ReadOnlyInner(formatter, (int)keyCount, cToken);
else
await InnerType.Read(formatter, (int)keyCount, cToken);
var valueCount = BitConverter.ToInt64(await formatter.ReadBytes(8, -1, cToken), 0);
Indices = new int[rows];
for (var i = 0; i < rows; i++) Indices[i] = BitConverter.ToInt32(await formatter.ReadBytes(_keySize, 4, cToken), 0);
Expand All @@ -67,4 +73,4 @@ public override void ValuesFromConst(IEnumerable objects) {
InnerType.NullableValuesFromConst(objects);
Indices = new int[InnerType.Rows];
}
}
}
12 changes: 11 additions & 1 deletion ClickHouse.Ado/Impl/ColumnTypes/NullableColumnType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,23 @@ public override async Task Write(ProtocolFormatter formatter, int rows, Cancella
await new SimpleColumnType<byte>(Nulls.Select(x => x ? (byte)1 : (byte)0).ToArray()).Write(formatter, rows, cToken);
await InnerType.Write(formatter, rows, cToken);
}

public async Task WriteOnlyInner(ProtocolFormatter formatter, int rows, CancellationToken cToken) {
Debug.Assert(Rows == rows, "Row count mismatch!");
await InnerType.Write(formatter, rows, cToken);
}

internal override async Task Read(ProtocolFormatter formatter, int rows, CancellationToken cToken) {
var nullStatuses = new SimpleColumnType<byte>();
await nullStatuses.Read(formatter, rows, cToken);
Nulls = nullStatuses.Data.Select(x => x != 0).ToArray();
await InnerType.Read(formatter, rows, cToken);
}

internal async Task ReadOnlyInner(ProtocolFormatter formatter, int rows, CancellationToken cToken) {
Nulls = new bool[rows];
await InnerType.Read(formatter, rows, cToken);
}

public override void ValueFromConst(Parser.ValueType val) {
Nulls = new[] { val.StringValue == null && val.ArrayValue == null };
Expand Down Expand Up @@ -63,4 +73,4 @@ public override void ValuesFromConst(IEnumerable objects) {
}

public bool IsNull(int currentRow) => Nulls[currentRow];
}
}
46 changes: 46 additions & 0 deletions ClickHouse.Test/Test_121_LowCardinalityNullableString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using ClickHouse.Ado;
using NUnit.Framework;

namespace ClickHouse.Test;

[TestFixture]
public class Test_121_LowCardinalityNullableString {
[OneTimeSetUp]
public void CreateStructures() {
using (var cnn = ConnectionHandler.GetConnection()) {
cnn.CreateCommand("DROP TABLE IF EXISTS test_121_lowcardinality").ExecuteNonQuery();
cnn.CreateCommand("CREATE TABLE test_121_lowcardinality (a LowCardinality(Nullable(String))) ENGINE = Memory").ExecuteNonQuery();
}

Thread.Sleep(1000);
}

[Test]
public void Test() {
using (var cnn = ConnectionHandler.GetConnection()) {
var items = new List<string>();
for (var i = 0; i < 1000; i++)
items.Add(((char)('A' + i % 20)).ToString());
items.Add(null);
items.Add("");
var result = cnn.CreateCommand("INSERT INTO test_121_lowcardinality (a) VALUES @bulk").AddParameter("bulk", DbType.Object, items.Select(x => (object)new object[] { x }).ToArray()).ExecuteNonQuery();

var values = new List<string>();
using (var cmd = cnn.CreateCommand("SELECT a FROM test_121_lowcardinality"))
using (var reader = cmd.ExecuteReader()) {
reader.ReadAll(r => { values.Add(r.GetString(0)); });
}

//Clickhouse has crazy support for LowCardinality<Nullable<something>>
//items[1000] = "";
//Assert.AreEqual(items.Count, values.Count);
//for (var i = 0; i < items.Count; i++)
//Assert.AreEqual(items[i], values[i]);
//Assert.IsTrue(items.SequenceEqual(values));
}
}
}

0 comments on commit bef6f54

Please sign in to comment.