Skip to content

6.1 Preview 1: SqlDataReader.GetChars ignoring BufferIndex? #3331

@Tornhoof

Description

@Tornhoof

Describe the bug

I tried the new 6.1 PReview1 bits, a few of my tests failed with wrong data output, I tracked it down to sqlDataReader.GetChars apparently ignoring the buffer index. In the GetPooledChars loop below the buffer is always written at bufferIndex 0 ignoring the offset parameter. If you run the same code with 6.0.2 it works.

To reproduce

using System.Buffers;
using System.Data;
using Microsoft.Data.SqlClient;

namespace SqlRepro
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            const CommandBehavior commandBehavior = CommandBehavior.SequentialAccess | CommandBehavior.SingleResult;
            await using var sqlConnection = new SqlConnection("Server=localhost;Database=DatabaseTests;TrustServerCertificate=True;Trusted_Connection=True;");
            await sqlConnection.OpenAsync();
            const int length = 32000;
            const string sqlCharWithArg = "SELECT CONVERT(BIGINT, 1) AS [Id], CONVERT(NVARCHAR(MAX), @input) AS [Value];";
            var input = string.Create(length, length, (span, state) =>
            {
                for (int i = 0; i < state; i++)
                {
                    span[i] = (char) Random.Shared.Next(0x30, 0x5A);
                }
            });
            await using var sqlCommand = new SqlCommand();
            sqlCommand.Connection = sqlConnection;
            sqlCommand.CommandTimeout = 0;
            sqlCommand.CommandText = sqlCharWithArg;
            sqlCommand.Parameters.Add(new SqlParameter("@input", SqlDbType.NVarChar, -1) {Value = input});
            await using var sqlReader = await sqlCommand.ExecuteReaderAsync(commandBehavior);
            var succ = await sqlReader.ReadAsync();
            long id = sqlReader.GetInt64(0);
            if (id != 1)
            {
                Console.WriteLine("Id not 1");
            }

            var sliced = GetPooledChars(sqlReader, 1);
            if (!sliced.SequenceEqual(input.ToCharArray()))
            {
                Console.WriteLine("sliced != input");
            }
        }

        private static char[] GetPooledChars(SqlDataReader sqlDataReader, int ordinal)
        {
            var buffer = ArrayPool<char>.Shared.Rent(8192);
            int offset = 0;
            while (true)
            {
                var read = (int) sqlDataReader.GetChars(ordinal, offset, buffer, offset, buffer.Length - offset);
                // in the second iteration, the buffer is already overwritten from offset 0, bufferIndex ignored?
                if (read == 0)
                {
                    break;
                }

                offset += read;

                if (buffer.Length - offset < 128)
                {
                    buffer = Resize(buffer);
                }
            }

            var sliced = buffer.AsSpan(0, offset).ToArray();
            ArrayPool<char>.Shared.Return(buffer);
            return sliced;

            static char[] Resize(char[] buffer)
            {
                var newBuffer = ArrayPool<char>.Shared.Rent(buffer.Length * 2);
                Array.Copy(buffer, newBuffer, buffer.Length);
                ArrayPool<char>.Shared.Return(buffer);
                return newBuffer;
            }
        }
    }
}
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0-preview1.25120.4" />
  </ItemGroup>

</Project>

Expected behavior

Slice should be the same as input in the repro

Further technical details

Microsoft.Data.SqlClient version: 6.1.0-preview1.25120.4
.NET target: 9.0
SQL Server version: SQL Server 2022
Operating system: Windows 11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions