Skip to content

Commit 2573547

Browse files
committed
feat: YdbList struct-mode (List<Struct>)
1 parent 4c6d452 commit 2573547

File tree

2 files changed

+269
-300
lines changed

2 files changed

+269
-300
lines changed

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

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,78 @@
44
namespace Ydb.Sdk.Value;
55

66
/// <summary>
7-
/// A wrapper above the list of YDB values. For bulk operations, it is used as
8-
/// <c>List&lt;Struct&lt;...&gt;&gt;</c> with the fields in the same order as <c>columns</c>.
7+
/// Universal wrapper for YDB lists.
8+
/// <para>
9+
/// - Plain mode (back-compat): wraps any <see cref="IEnumerable{object}"/> and produces <c>List&lt;T&gt;</c>.
10+
/// </para>
11+
/// <para>
12+
/// - Struct mode: builds <c>List&lt;Struct&lt;...&gt;&gt;</c> without using <c>YdbValue.MakeStruct</c> on the outside.
13+
/// You define column names (and optional types) and push rows positionally.
14+
/// </para>
915
/// </summary>
1016
public sealed class YdbList
1117
{
12-
private readonly IReadOnlyList<object> _items;
18+
// -------- Plain mode --------
19+
private readonly IReadOnlyList<object>? _items;
1320

21+
// -------- Struct mode --------
22+
private readonly string[]? _columns;
23+
private readonly YdbDbType[]? _types;
24+
private readonly List<object?[]>? _rows;
25+
26+
/// <summary>
27+
/// Plain mode constructor (kept for backward compatibility).
28+
/// Produces <c>List&lt;T&gt;</c> by inferring element types.
29+
/// </summary>
1430
public YdbList(IEnumerable<object> items)
1531
{
1632
_items = items as IReadOnlyList<object> ?? items.ToList();
1733
}
1834

35+
/// <summary>
36+
/// Start Struct mode with column names (types will be inferred from the first non-null row).
37+
/// </summary>
38+
public static YdbList Struct(params string[] columns) => new(columns, null);
39+
40+
/// <summary>
41+
/// Start Struct mode with column names and explicit YDB types (same length as <paramref name="columns"/>).
42+
/// Use explicit types if you plan to pass <c>null</c> values and want typed NULLs.
43+
/// </summary>
44+
public static YdbList Struct(string[] columns, YdbDbType[]? types) => new(columns, types);
45+
46+
private YdbList(string[] columns, YdbDbType[]? types)
47+
{
48+
if (types is not null && types.Length != columns.Length)
49+
throw new ArgumentException("Length of 'types' must match length of 'columns'.", nameof(types));
50+
51+
_columns = columns;
52+
_types = types;
53+
_rows = new List<object?[]>();
54+
}
55+
56+
/// <summary>
57+
/// Add one positional row (Struct mode). Values must match the number of columns.
58+
/// </summary>
59+
public YdbList AddRow(params object?[] values)
60+
{
61+
EnsureStruct();
62+
if (values.Length != _columns!.Length)
63+
throw new ArgumentException($"Expected {_columns.Length} values, got {values.Length}.");
64+
_rows!.Add(values);
65+
return this;
66+
}
67+
68+
/// <summary>
69+
/// Converts this wrapper to a YDB <see cref="TypedValue"/>.
70+
/// In plain mode returns <c>List&lt;T&gt;</c>; in struct mode returns <c>List&lt;Struct&lt;...&gt;&gt;</c>.
71+
/// </summary>
1972
internal TypedValue ToTypedValue()
73+
=> _columns is null ? ToTypedValuePlain() : ToTypedValueStruct();
74+
75+
// -------- Implementation: plain mode --------
76+
private TypedValue ToTypedValuePlain()
2077
{
21-
var typed = new List<TypedValue>(_items.Count);
78+
var typed = new List<TypedValue>(_items!.Count);
2279
foreach (var item in _items)
2380
{
2481
var tv = item switch
@@ -32,4 +89,87 @@ internal TypedValue ToTypedValue()
3289

3390
return typed.List();
3491
}
92+
93+
// -------- Implementation: struct mode --------
94+
private TypedValue ToTypedValueStruct()
95+
{
96+
if (_rows!.Count == 0 && (_types is null || _types.All(t => t == YdbDbType.Unspecified)))
97+
throw new InvalidOperationException(
98+
"Cannot infer Struct schema from an empty list without explicit YdbDbType hints.");
99+
100+
var memberTypes = new List<Type>(_columns!.Length);
101+
for (var i = 0; i < _columns.Length; i++)
102+
{
103+
if (_types is not null && _types[i] != YdbDbType.Unspecified)
104+
{
105+
var tv = new YdbParameter { YdbDbType = _types[i] }.TypedValue;
106+
memberTypes.Add(tv.Type);
107+
continue;
108+
}
109+
110+
var sample = (from r in _rows where r[i] is not null and not DBNull select r[i]).FirstOrDefault();
111+
if (sample is null)
112+
throw new InvalidOperationException(
113+
$"Column '{_columns[i]}' has only nulls and no explicit YdbDbType. Provide a type hint.");
114+
115+
var inferred = new YdbParameter { Value = sample }.TypedValue;
116+
memberTypes.Add(inferred.Type);
117+
}
118+
119+
var structType = new StructType
120+
{
121+
Members =
122+
{
123+
_columns.Select((name, idx) => new StructMember
124+
{
125+
Name = name,
126+
Type = memberTypes[idx]
127+
})
128+
}
129+
};
130+
131+
var ydbRows = new List<Ydb.Value>(_rows.Count);
132+
foreach (var r in _rows)
133+
{
134+
var fields = new List<Ydb.Value>(_columns.Length);
135+
for (var i = 0; i < _columns.Length; i++)
136+
{
137+
var v = r[i];
138+
139+
if (_types is not null && _types[i] != YdbDbType.Unspecified)
140+
{
141+
var tv = new YdbParameter { YdbDbType = _types[i], Value = v }.TypedValue;
142+
fields.Add(tv.Value);
143+
}
144+
else
145+
{
146+
if (v is null || v == DBNull.Value)
147+
throw new InvalidOperationException(
148+
$"Column '{_columns[i]}' has null value but no explicit YdbDbType. Provide a type hint.");
149+
150+
var tv = v switch
151+
{
152+
YdbValue yv => yv.GetProto(),
153+
YdbParameter p => p.TypedValue,
154+
_ => new YdbParameter { Value = v }.TypedValue
155+
};
156+
fields.Add(tv.Value);
157+
}
158+
}
159+
ydbRows.Add(new Ydb.Value { Items = { fields } });
160+
}
161+
162+
return new TypedValue
163+
{
164+
Type = new Type { ListType = new ListType { Item = new Type { StructType = structType } } },
165+
Value = new Ydb.Value { Items = { ydbRows } }
166+
};
167+
}
168+
169+
private void EnsureStruct()
170+
{
171+
if (_columns is null)
172+
throw new InvalidOperationException(
173+
"This YdbList was created in plain mode. Use YdbList.Struct(...) to build List<Struct<...>>.");
174+
}
35175
}

0 commit comments

Comments
 (0)