private async ValueTask WriteRow(IReadOnlyCollection <object?> values, bool async, CancellationToken cancellationToken)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            if (values.Count != _columns.Count)
            {
                throw new ArgumentException("The number of values must be equal to the number of columns.");
            }

            var columnWriters = new List <IClickHouseColumnWriter>(_columns.Count);

            foreach (var value in values)
            {
                int i = columnWriters.Count;

                var columnInfo = _columns[i];
                SingleRowColumnWriterDispatcher dispatcher;
                Type valueType;
                if (value != null && !(value is DBNull))
                {
                    dispatcher = new SingleRowColumnWriterDispatcher(value, columnInfo, _columnSettings?[i]);
                    valueType  = value.GetType();
                }
                else if (columnInfo.TypeInfo.TypeName != "Nullable")
                {
                    throw new ClickHouseException(ClickHouseErrorCodes.ColumnMismatch, $"The column \"{columnInfo.Name}\" at the position {i} doesn't support nulls.");
                }
                else
                {
                    dispatcher = new SingleRowColumnWriterDispatcher(null, columnInfo, _columnSettings?[i]);
                    valueType  = columnInfo.TypeInfo.GetFieldType();
                }

                IClickHouseColumnWriter columnWriter;
                try
                {
                    columnWriter = TypeDispatcher.Dispatch(valueType, dispatcher);
                }
                catch (ClickHouseException ex)
                {
                    throw new ClickHouseException(ex.ErrorCode, $"Column \"{columnInfo.Name}\" (position {i}): {ex.Message}", ex);
                }

                columnWriters.Add(columnWriter);
            }

            var table = new ClickHouseTableWriter(string.Empty, 1, columnWriters);

            await SendTable(table, async, cancellationToken);
        }
        private async ValueTask SendTable(ClickHouseTableWriter table, bool async, CancellationToken cancellationToken)
        {
            try
            {
                await _session.SendTable(table, async, cancellationToken);
            }
            catch (ClickHouseHandledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                var aggrEx = await _session.SetFailed(ex, false, async);

                if (aggrEx != null)
                {
                    throw aggrEx;
                }

                throw;
            }
        }
        private async ValueTask WriteTable(IReadOnlyList <object?> columns, int rowCount, bool async, CancellationToken cancellationToken)
        {
            if (columns == null)
            {
                throw new ArgumentNullException(nameof(columns));
            }
            if (columns.Count != _columns.Count)
            {
                throw new ArgumentException("The number of columns for writing must be equal to the number of columns in the table.", nameof(columns));
            }
            if (rowCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(rowCount));
            }
            if (rowCount == 0)
            {
                throw new ArgumentException("The number of rows must be grater than zero.", nameof(rowCount));
            }

            if (IsClosed)
            {
                throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The writer is closed.");
            }

            var writers = new List <IClickHouseColumnWriter>(_columns.Count);

            for (int i = 0; i < _columns.Count; i++)
            {
                var column     = columns[i];
                var columnInfo = _columns[i];

                if (column == null)
                {
                    if (!columnInfo.TypeInfo.TypeName.StartsWith("Nullable"))
                    {
                        throw new ClickHouseException(ClickHouseErrorCodes.ColumnMismatch, $"The column \"{columnInfo.Name}\" at the position {i} doesn't support nulls.");
                    }

                    var constColumn = TypeDispatcher.Dispatch(columnInfo.TypeInfo.GetFieldType(), new NullColumnWriterDispatcher(columnInfo, _columnSettings?[i], rowCount));
                    writers.Add(constColumn);
                    continue;
                }

                var columnType = column.GetType();

                bool isEnumerable = false;
                Type?enumerable = null, asyncEnumerable = null, readOnlyList = null, list = null;
                foreach (var ifs in columnType.GetInterfaces())
                {
                    if (ifs == typeof(IEnumerable))
                    {
                        isEnumerable = true;
                    }
                    else if (ifs.IsGenericType)
                    {
                        var ifsDefinition = ifs.GetGenericTypeDefinition();
                        if (ifsDefinition == typeof(IEnumerable <>))
                        {
                            enumerable ??= ifs;
                        }
                        else if (ifsDefinition == typeof(IAsyncEnumerable <>))
                        {
                            asyncEnumerable ??= ifs;
                        }
                        else if (ifsDefinition == typeof(IReadOnlyList <>))
                        {
                            readOnlyList ??= ifs;
                        }
                        else if (ifsDefinition == typeof(IList <>))
                        {
                            list ??= ifs;
                        }
                    }
                }

                Type dispatchedElementType;
                if (readOnlyList != null)
                {
                    dispatchedElementType = readOnlyList.GetGenericArguments()[0];
                }
                else if (list != null)
                {
                    dispatchedElementType = list.GetGenericArguments()[0];
                }
                else
                {
                    if (asyncEnumerable != null)
                    {
                        if (async)
                        {
                            var genericArg      = asyncEnumerable.GetGenericArguments()[0];
                            var asyncDispatcher = new AsyncColumnWriterDispatcher(column, columnInfo, _columnSettings?[i], rowCount, i, cancellationToken);
                            var asyncColumn     = await TypeDispatcher.Dispatch(genericArg, asyncDispatcher);

                            writers.Add(asyncColumn);
                            continue;
                        }

                        if (!isEnumerable && enumerable == null)
                        {
                            throw new ClickHouseException(
                                      ClickHouseErrorCodes.ColumnMismatch,
                                      $"The column \"{columnInfo.Name}\" at the position {i} implements interface \"{asyncEnumerable}\". Call async method \"{nameof(WriteTableAsync)}\".");
                        }
                    }

                    if (enumerable != null)
                    {
                        dispatchedElementType = enumerable.GetGenericArguments()[0];
                    }
                    else if (isEnumerable)
                    {
                        dispatchedElementType = columnInfo.TypeInfo.GetFieldType();
                    }
                    else
                    {
                        throw new ClickHouseException(ClickHouseErrorCodes.ColumnMismatch, $"The column \"{columnInfo.Name}\" at the position {i} is not a collection.");
                    }
                }

                var dispatcher   = new ColumnWriterDispatcher(column, columnInfo, _columnSettings?[i], rowCount, i);
                var columnWriter = TypeDispatcher.Dispatch(dispatchedElementType, dispatcher);
                writers.Add(columnWriter);
            }

            var table = new ClickHouseTableWriter(string.Empty, rowCount, writers);

            await SendTable(table, async, cancellationToken);
        }