Skip to content

Commit aab0035

Browse files
committed
made an issue
1 parent 35118f6 commit aab0035

File tree

6 files changed

+356
-343
lines changed

6 files changed

+356
-343
lines changed

src/Ydb.Sdk/src/Ado/BulkUpsert/BulkUpsertImporter.cs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,40 @@ internal BulkUpsertImporter(
3434
/// <summary>
3535
/// Add a single row to the current BulkUpsert batch.
3636
/// </summary>
37-
/// <param name="values">Column values in the same order as the configured <c>columns</c>.</param>
37+
/// <param name="values">Values in the same order as the configured <c>columns</c>.</param>
3838
/// <remarks>
39-
/// Supported element types: <see cref="YdbValue"/>, <see cref="YdbParameter"/>, <see cref="YdbList"/> (as-is);
40-
/// other CLR values are converted via <see cref="YdbParameter"/>.
39+
/// Supported per-cell types: <see cref="YdbValue"/>, <see cref="YdbParameter"/>.
40+
/// Other CLR values are converted via <see cref="YdbParameter"/>.
41+
/// Passing <see cref="YdbList"/> as a column value is not supported (tables do not accept list-typed columns).
42+
/// Use <c>AddListAsync(YdbList)</c> to append many rows from a list parameter.
4143
/// </remarks>
44+
/// <exception cref="ArgumentException">Thrown when the number of values differs from the number of columns.</exception>
45+
/// <exception cref="InvalidOperationException">Thrown when a value cannot be mapped to a YDB type.</exception>
4246
/// <example>
4347
/// <code>
4448
/// // columns: ["Id", "Name"]
4549
/// await importer.AddRowAsync(1, "Alice");
50+
/// await importer.AddRowAsync(2, "Bob");
4651
/// </code>
4752
/// </example>
48-
/// <exception cref="ArgumentException">When the number of values doesn't equal the number of columns.</exception>
49-
/// <exception cref="InvalidOperationException">When a value cannot be mapped to a YDB type.</exception>
5053
public async ValueTask AddRowAsync(params object[] values)
5154
{
5255
if (values.Length != _columns.Count)
5356
throw new ArgumentException("Values count must match columns count.", nameof(values));
5457

5558
var ydbValues = values.Select(v => v switch
5659
{
57-
YdbValue ydbValue => ydbValue.GetProto(),
60+
YdbValue ydbValue => ydbValue.GetProto(),
5861
YdbParameter param => param.TypedValue,
59-
YdbList list => list.ToTypedValue(),
62+
YdbList => throw new ArgumentException(
63+
"YdbList cannot be used as a column value. Use AddListAsync(YdbList) to append multiple rows.",
64+
nameof(values)),
6065
_ => new YdbParameter { Value = v }.TypedValue
6166
}).ToArray();
6267

6368
var protoStruct = new Ydb.Value();
64-
foreach (var value in ydbValues)
65-
protoStruct.Items.Add(value.Value);
69+
foreach (var tv in ydbValues)
70+
protoStruct.Items.Add(tv.Value);
6671

6772
var rowSize = protoStruct.CalculateSize();
6873

@@ -79,24 +84,17 @@ public async ValueTask AddRowAsync(params object[] values)
7984
}
8085

8186
/// <summary>
82-
/// Add multiple rows from a single <see cref="YdbList"/> parameter.
87+
/// Add multiple rows from a <see cref="YdbList"/> shaped as <c>List&lt;Struct&lt;...&gt;&gt;</c>.
88+
/// Struct member names and order must exactly match the configured <c>columns</c>.
8389
/// </summary>
84-
/// <remarks>
85-
/// Expects <c>List&lt;Struct&lt;...&gt;&gt;</c>; struct member names and order must exactly match the configured <c>columns</c>.
86-
/// Example: <c>columns=["Id","Name"]</c> → <c>List&lt;Struct&lt;Id:Int64, Name:Utf8&gt;&gt;</c>.
87-
/// </remarks>
90+
/// <param name="list">Rows as <c>List&lt;Struct&lt;...&gt;&gt;</c> with the exact column names and order.</param>
91+
/// <exception cref="ArgumentException">
92+
/// Thrown when the struct column set, order, or count does not match the importer’s <c>columns</c>.
93+
/// </exception>
8894
public async ValueTask AddListAsync(YdbList list)
8995
{
9096
var tv = list.ToTypedValue();
9197

92-
if (tv.Type.TypeCase != Type.TypeOneofCase.ListType ||
93-
tv.Type.ListType.Item.TypeCase != Type.TypeOneofCase.StructType)
94-
{
95-
throw new ArgumentException(
96-
"BulkUpsertImporter.AddListAsync expects a YdbList with a value like List<Struct<...>>",
97-
nameof(list));
98-
}
99-
10098
var incomingStruct = tv.Type.ListType.Item.StructType;
10199

102100
if (incomingStruct.Members.Count != _columns.Count)
@@ -130,6 +128,10 @@ public async ValueTask AddListAsync(YdbList list)
130128
/// <summary>
131129
/// Flush the current batch via BulkUpsert. No-op if the batch is empty.
132130
/// </summary>
131+
/// <remarks>
132+
/// Uses the collected struct schema from the first added row (or the provided list) and sends
133+
/// the accumulated rows in a single BulkUpsert request.
134+
/// </remarks>
133135
public async ValueTask FlushAsync()
134136
{
135137
if (_rows.Count == 0)

src/Ydb.Sdk/src/Ado/BulkUpsert/IBulkUpsertImporter.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,30 @@
22

33
namespace Ydb.Sdk.Ado.BulkUpsert;
44

5+
/// <summary>
6+
/// Bulk upsert importer API: add rows and flush them to YDB in batches.
7+
/// </summary>
58
public interface IBulkUpsertImporter
69
{
7-
/// <summary>Add a single row to the batch. Values must match the importer column order.</summary>
8-
/// <param name="row">Column values in the same order as the configured <c>columns</c>.</param>
10+
/// <summary>
11+
/// Add a single row to the batch. Values must match the importer’s column order.
12+
/// </summary>
13+
/// <param name="row">Values in the same order as the configured <c>columns</c>.</param>
14+
/// <exception cref="ArgumentException">Thrown when the number of values differs from the number of columns.</exception>
915
ValueTask AddRowAsync(params object[] row);
1016

1117
/// <summary>
12-
/// Add many rows from <see cref="YdbList"/> (shape: <c>List&lt;Struct&lt;...&gt;&gt;</c>).
18+
/// Add multiple rows from a <see cref="YdbList"/> shaped as <c>List&lt;Struct&lt;...&gt;&gt;</c>.
1319
/// Struct member names and order must exactly match the configured <c>columns</c>.
1420
/// </summary>
21+
/// <param name="list">Rows as <c>List&lt;Struct&lt;...&gt;&gt;</c> with the exact column names and order.</param>
22+
/// <exception cref="ArgumentException">
23+
/// Thrown when the struct column set, order, or count does not match the importer’s <c>columns</c>.
24+
/// </exception>
1525
ValueTask AddListAsync(YdbList list);
1626

17-
/// <summary>Flush the current batch via BulkUpsert (no-op if empty).</summary>
27+
/// <summary>
28+
/// Flush the current batch via BulkUpsert. No-op if the batch is empty.
29+
/// </summary>
1830
ValueTask FlushAsync();
1931
}

src/Ydb.Sdk/src/Ado/YdbParameter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,8 @@ internal TypedValue TypedValue
149149
YdbDbType.Double => MakeDouble(value),
150150
YdbDbType.Decimal when value is decimal decimalValue => Decimal(decimalValue),
151151
YdbDbType.Bytes => MakeBytes(value),
152-
YdbDbType.Json when value is string stringJsonValue => stringJsonValue.Json(),
153-
YdbDbType.JsonDocument when value is string stringJsonDocumentValue => stringJsonDocumentValue
154-
.JsonDocument(),
152+
YdbDbType.Json when value is string stringValue => stringValue.Json(),
153+
YdbDbType.JsonDocument when value is string stringValue => stringValue.JsonDocument(),
155154
YdbDbType.Uuid when value is Guid guidValue => guidValue.Uuid(),
156155
YdbDbType.Date => MakeDate(value),
157156
YdbDbType.DateTime when value is DateTime dateTimeValue => dateTimeValue.Datetime(),
@@ -290,7 +289,8 @@ private TypedValue NullTypedValue()
290289
}
291290

292291
throw new InvalidOperationException(
293-
"Writing value of 'null' is not supported without explicit mapping to the YdbDbType");
292+
"Writing value of 'null' is not supported without explicit mapping to the YdbDbType"
293+
);
294294
}
295295

296296
private InvalidOperationException ValueTypeNotSupportedException =>

src/Ydb.Sdk/src/Value/YdbList.cs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,34 @@
55
namespace Ydb.Sdk.Value;
66

77
/// <summary>
8-
/// Struct-only builder for YDB <c>List&lt;Struct&lt;...&gt;&gt;</c>.
9-
/// Works directly with protobuf:
10-
/// - Each call to <see cref="AddRow(object?[])"/> converts values into protobuf cells (<see cref="Ydb.Value"/>) and stores a row immediately.
11-
/// - The struct schema (<see cref="StructType"/>) is derived from column type hints or from the first non-null sample per column.
12-
/// - If a column has at least one <c>null</c>, its type becomes <c>Optional&lt;T&gt;</c>; individual null cells are encoded via <see cref="NullValue.NullValue"/>.
8+
/// Builder for YDB values shaped as <c>List&lt;Struct&lt;...&gt;&gt;</c>, working directly with protobuf.
9+
/// Each call to <see cref="AddRow(object?[])"/> converts input values into protobuf cells and stores the row.
10+
/// The struct schema is derived from explicit type hints or from the first non-null sample per column.
11+
/// If a column contains at least one <c>null</c>, its member type becomes <c>Optional&lt;T&gt;</c>.
1312
/// </summary>
1413
public sealed class YdbList
1514
{
1615
private readonly string[] _columns;
17-
private readonly YdbDbType[]? _typeHints;
16+
private readonly YdbDbType[] _typeHints;
1817

1918
private readonly List<Ydb.Value> _rows = new();
2019

2120
private readonly Type?[] _observedBaseTypes;
2221
private readonly bool[] _sawNull;
2322

2423
/// <summary>
25-
/// Create a struct-mode list with column names; types will be inferred from the
26-
/// first non-null value per column (columns with any nulls become Optional&lt;T&gt;).
24+
/// Create a struct-mode list with column names. Types will be inferred from the
25+
/// first non-null value per column (columns with any nulls become <c>Optional&lt;T&gt;</c>).
2726
/// </summary>
27+
/// <param name="columns">Struct member names, in order.</param>
2828
public static YdbList Struct(params string[] columns) => new(columns);
2929

3030
/// <summary>
31-
/// Create a struct-mode list with column names and explicit YDB type hints
32-
/// (array length must match <paramref name="columns"/>). Columns with any nulls
33-
/// will be emitted as Optional&lt;hintedType&gt;.
31+
/// Create a struct-mode list with column names and explicit YDB type hints. The array length must match <paramref name="columns"/>.
32+
/// Columns that contain <c>null</c> are emitted as <c>Optional&lt;hintedType&gt;</c>.
3433
/// </summary>
34+
/// <param name="columns">Struct member names, in order.</param>
35+
/// <param name="types">YDB type hints for each column (same length as <paramref name="columns"/>).</param>
3536
public static YdbList Struct(string[] columns, YdbDbType[] types) => new(columns, types);
3637

3738
/// <summary>
@@ -45,7 +46,7 @@ private YdbList(string[] columns, YdbDbType[]? types = null)
4546
throw new ArgumentException("Length of 'types' must match length of 'columns'.", nameof(types));
4647

4748
_columns = columns;
48-
_typeHints = types;
49+
_typeHints = types ?? Enumerable.Repeat(YdbDbType.Unspecified, columns.Length).ToArray();
4950
_observedBaseTypes = new Type[_columns.Length];
5051
_sawNull = new bool[_columns.Length];
5152
}
@@ -54,6 +55,8 @@ private YdbList(string[] columns, YdbDbType[]? types = null)
5455
/// Add a single positional row. The number of values must match the number of columns.
5556
/// Values are converted to protobuf cells and the row is stored immediately.
5657
/// </summary>
58+
/// <param name="values">Row values in the same order as the declared columns.</param>
59+
/// <exception cref="ArgumentException">Thrown when the number of values differs from the number of columns.</exception>
5760
public YdbList AddRow(params object?[] values)
5861
{
5962
if (values.Length != _columns.Length)
@@ -92,13 +95,17 @@ public YdbList AddRow(params object?[] values)
9295
}
9396

9497
/// <summary>
95-
/// Convert to a YDB <see cref="TypedValue"/> shaped as <c>List&lt;Struct&lt;...&gt;&gt;</c>.
96-
/// Columns that observed <c>null</c> values are emitted as <c>Optional&lt;T&gt;</c>;
97-
/// individual <c>null</c> cells are encoded via <see cref="NullValue.NullValue"/>.
98+
/// Convert the accumulated rows into a YDB <see cref="TypedValue"/> with shape <c>List&lt;Struct&lt;...&gt;&gt;</c>.
99+
/// Columns that observed <c>null</c> values are emitted as <c>Optional&lt;T&gt;</c>.
98100
/// </summary>
101+
/// <returns>TypedValue representing <c>List&lt;Struct&lt;...&gt;&gt;</c> and the collected data rows.</returns>
102+
/// <exception cref="InvalidOperationException">
103+
/// Thrown when the schema cannot be inferred (e.g., empty list without type hints, or a column has only nulls
104+
/// and no explicit type hint).
105+
/// </exception>
99106
internal TypedValue ToTypedValue()
100107
{
101-
if (_rows.Count == 0 && (_typeHints is null || _typeHints.All(t => t == YdbDbType.Unspecified)))
108+
if (_rows.Count == 0)
102109
throw new InvalidOperationException(
103110
"Cannot infer Struct schema from an empty list without explicit YdbDbType hints.");
104111

@@ -109,7 +116,7 @@ internal TypedValue ToTypedValue()
109116
{
110117
Type? baseType;
111118

112-
if (_typeHints is not null && _typeHints[i] != YdbDbType.Unspecified)
119+
if (_typeHints[i] != YdbDbType.Unspecified)
113120
{
114121
baseType = new YdbParameter { YdbDbType = _typeHints[i], Value = DBNull.Value }.TypedValue.Type;
115122

0 commit comments

Comments
 (0)