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

    Bug! 🐛Issues that are bugs in the drivers we maintain.Triage Done ✔️Issues that are triaged by dev team and are in investigation.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions