44namespace 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<Struct<...>></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<T></c>.
10+ /// </para>
11+ /// <para>
12+ /// - Struct mode: builds <c>List<Struct<...>></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>
1016public 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<T></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<T></c>; in struct mode returns <c>List<Struct<...>></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