@@ -308,4 +308,187 @@ private List<Task> GenerateTasks() => Enumerable.Range(0, 100).Select(async i =>
308308
309309 protected override async Task OnDisposeAsync ( ) =>
310310 await YdbConnection . ClearPool ( new YdbConnection ( _connectionStringTls ) ) ;
311+
312+ [ Fact ]
313+ public async Task BulkUpsertImporter_HappyPath_Add_Flush ( )
314+ {
315+ var tableName = $ "BulkImporter_{ Guid . NewGuid ( ) : N} ";
316+
317+ var conn = new YdbConnection ( _connectionStringTls ) ;
318+ await conn . OpenAsync ( ) ;
319+ try
320+ {
321+ await using ( var createCmd = conn . CreateCommand ( ) )
322+ {
323+ createCmd . CommandText = $@ "
324+ CREATE TABLE { tableName } (
325+ Id Int32,
326+ Name Utf8,
327+ PRIMARY KEY (Id)
328+ )" ;
329+ await createCmd . ExecuteNonQueryAsync ( ) ;
330+ }
331+
332+ var columns = new [ ] { "Id" , "Name" } ;
333+
334+ var importer = conn . BeginBulkUpsertImport ( tableName , columns ) ;
335+
336+ await importer . AddRowAsync ( [ YdbValue . MakeInt32 ( 1 ) , YdbValue . MakeUtf8 ( "Alice" ) ] ) ;
337+ await importer . AddRowAsync ( [ YdbValue . MakeInt32 ( 2 ) , YdbValue . MakeUtf8 ( "Bob" ) ] ) ;
338+ await importer . FlushAsync ( ) ;
339+
340+ await using ( var checkCmd = conn . CreateCommand ( ) )
341+ {
342+ checkCmd . CommandText = $ "SELECT COUNT(*) FROM { tableName } ";
343+ var count = Convert . ToInt32 ( await checkCmd . ExecuteScalarAsync ( ) ) ;
344+ Assert . Equal ( 2 , count ) ;
345+ }
346+
347+ importer = conn . BeginBulkUpsertImport ( tableName , columns ) ;
348+ await importer . AddRowAsync ( [ YdbValue . MakeInt32 ( 3 ) , YdbValue . MakeUtf8 ( "Charlie" ) ] ) ;
349+ await importer . AddRowAsync ( [ YdbValue . MakeInt32 ( 4 ) , YdbValue . MakeUtf8 ( "Diana" ) ] ) ;
350+ await importer . FlushAsync ( ) ;
351+
352+ await using ( var checkCmd = conn . CreateCommand ( ) )
353+ {
354+ checkCmd . CommandText = $ "SELECT Name FROM { tableName } ORDER BY Id";
355+ var names = new List < string > ( ) ;
356+ await using var reader = await checkCmd . ExecuteReaderAsync ( ) ;
357+ while ( await reader . ReadAsync ( ) )
358+ names . Add ( reader . GetString ( 0 ) ) ;
359+ Assert . Contains ( "Alice" , names ) ;
360+ Assert . Contains ( "Bob" , names ) ;
361+ Assert . Contains ( "Charlie" , names ) ;
362+ Assert . Contains ( "Diana" , names ) ;
363+ }
364+ }
365+ finally
366+ {
367+ await using var dropCmd = conn . CreateCommand ( ) ;
368+ dropCmd . CommandText = $ "DROP TABLE { tableName } ";
369+ await dropCmd . ExecuteNonQueryAsync ( ) ;
370+ }
371+ }
372+
373+ [ Fact ]
374+ public async Task BulkUpsertImporter_ThrowsOnInvalidRowCount ( )
375+ {
376+ var tableName = $ "BulkImporter_{ Guid . NewGuid ( ) : N} ";
377+ var conn = new YdbConnection ( _connectionStringTls ) ;
378+ await conn . OpenAsync ( ) ;
379+ try
380+ {
381+ await using ( var createCmd = conn . CreateCommand ( ) )
382+ {
383+ createCmd . CommandText = $@ "
384+ CREATE TABLE { tableName } (
385+ Id Int32,
386+ Name Utf8,
387+ PRIMARY KEY (Id)
388+ )" ;
389+ await createCmd . ExecuteNonQueryAsync ( ) ;
390+ }
391+
392+ var columns = new [ ] { "Id" , "Name" } ;
393+
394+ var importer = conn . BeginBulkUpsertImport ( tableName , columns ) ;
395+
396+ var badRow = new object ? [ ] { YdbValue . MakeInt32 ( 1 ) } ;
397+ await Assert . ThrowsAsync < ArgumentException > ( async ( ) => await importer . AddRowAsync ( [ badRow ] ) ) ;
398+
399+ await Assert . ThrowsAsync < ArgumentException > ( async ( ) =>
400+ {
401+ await importer . AddRowAsync ( [
402+ new object ? [ ] { YdbValue . MakeInt32 ( 2 ) }
403+ ] ) ;
404+ } ) ;
405+ }
406+ finally
407+ {
408+ await using var dropCmd = conn . CreateCommand ( ) ;
409+ dropCmd . CommandText = $ "DROP TABLE { tableName } ";
410+ await dropCmd . ExecuteNonQueryAsync ( ) ;
411+ }
412+ }
413+
414+ [ Fact ]
415+ public async Task BulkUpsertImporter_MultipleImporters_Parallel ( )
416+ {
417+ var table1 = $ "BulkImporter_{ Guid . NewGuid ( ) : N} _1";
418+ var table2 = $ "BulkImporter_{ Guid . NewGuid ( ) : N} _2";
419+
420+ var conn = new YdbConnection ( _connectionStringTls ) ;
421+ await conn . OpenAsync ( ) ;
422+ try
423+ {
424+ foreach ( var table in new [ ] { table1 , table2 } )
425+ {
426+ await using var createCmd = conn . CreateCommand ( ) ;
427+ createCmd . CommandText = $@ "CREATE TABLE { table } (
428+ Id Int32,
429+ Name Utf8,
430+ PRIMARY KEY (Id)
431+ )" ;
432+ await createCmd . ExecuteNonQueryAsync ( ) ;
433+ }
434+
435+ var columns = new [ ] { "Id" , "Name" } ;
436+
437+ await Task . WhenAll (
438+ Task . Run ( async ( ) =>
439+ {
440+ var importer = conn . BeginBulkUpsertImport ( table1 , columns ) ;
441+ var rows = Enumerable . Range ( 0 , 20 )
442+ . Select ( i => new object ? [ ] { YdbValue . MakeInt32 ( i ) , YdbValue . MakeUtf8 ( $ "A{ i } ") } )
443+ . ToArray ( ) ;
444+ foreach ( var row in rows )
445+ await importer . AddRowAsync ( row ) ;
446+ await importer . FlushAsync ( ) ;
447+ } ) ,
448+ Task . Run ( async ( ) =>
449+ {
450+ var importer = conn . BeginBulkUpsertImport ( table2 , columns ) ;
451+ var rows = Enumerable . Range ( 0 , 20 )
452+ . Select ( i => new object ? [ ] { YdbValue . MakeInt32 ( i ) , YdbValue . MakeUtf8 ( $ "B{ i } ") } )
453+ . ToArray ( ) ;
454+ foreach ( var row in rows )
455+ await importer . AddRowAsync ( row ) ;
456+ await importer . FlushAsync ( ) ;
457+ } )
458+ ) ;
459+
460+ foreach ( var table in new [ ] { table1 , table2 } )
461+ {
462+ await using var checkCmd = conn . CreateCommand ( ) ;
463+ checkCmd . CommandText = $ "SELECT COUNT(*) FROM { table } ";
464+ var count = Convert . ToInt32 ( await checkCmd . ExecuteScalarAsync ( ) ) ;
465+ Assert . Equal ( 20 , count ) ;
466+ }
467+ }
468+ finally
469+ {
470+ foreach ( var table in new [ ] { table1 , table2 } )
471+ {
472+ await using var dropCmd = conn . CreateCommand ( ) ;
473+ dropCmd . CommandText = $ "DROP TABLE { table } ";
474+ await dropCmd . ExecuteNonQueryAsync ( ) ;
475+ }
476+ }
477+ }
478+
479+ [ Fact ]
480+ public async Task BulkUpsertImporter_ThrowsOnNonexistentTable ( )
481+ {
482+ var tableName = $ "Nonexistent_{ Guid . NewGuid ( ) : N} ";
483+ var conn = new YdbConnection ( _connectionStringTls ) ;
484+ await conn . OpenAsync ( ) ;
485+
486+ var columns = new [ ] { "Id" , "Name" } ;
487+
488+ var importer = conn . BeginBulkUpsertImport ( tableName , columns ) ;
489+
490+ await importer . AddRowAsync ( [ YdbValue . MakeInt32 ( 1 ) , YdbValue . MakeUtf8 ( "NotExists" ) ] ) ;
491+
492+ await Assert . ThrowsAsync < YdbException > ( async ( ) => { await importer . FlushAsync ( ) ; } ) ;
493+ }
311494}
0 commit comments