Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,30 @@ public static explicit operator SQLLEN(long value)

public static implicit operator int(SQLLEN value)
{
return value._value.ToInt32();
long longValue = value.ToInt64();

// Handle cases where ODBC drivers incorrectly represent 32-bit -1 as 0x00000000FFFFFFFF
// instead of properly sign-extending to 0xFFFFFFFFFFFFFFFF on 64-bit platforms.
// This is a known issue with some drivers (e.g., Filemaker Pro).
// We check for common ODBC sentinel values that may have this issue.
if (longValue == 0x00000000FFFFFFFF)
{
// This is likely -1 (SQL_NULL_DATA) improperly represented
return -1;
}
if (longValue == 0x00000000FFFFFFFD)
{
// This is likely -3 (SQL_NTS) improperly represented
return -3;
}
if (longValue == 0x00000000FFFFFFFC)
{
// This is likely -4 (SQL_NO_TOTAL) improperly represented
return -4;
}

// For all other values, validate they fit in Int32 range
return checked((int)longValue);
}

public static explicit operator long(SQLLEN value)
Expand Down
245 changes: 245 additions & 0 deletions src/libraries/System.Data.Odbc/tests/SQLLENTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Xunit;

namespace System.Data.Odbc.Tests
{
public class SQLLENTests
{
[Fact]
public void SQLLEN_ImplicitConversion_HandlesMinusOne_64Bit()
{
// This test verifies that SQLLEN can properly handle the value 0x00000000FFFFFFFF
// which represents -1 in 32-bit but 4294967295 in 64-bit representation.
// This scenario occurs with some ODBC drivers (e.g., Filemaker Pro) that incorrectly
// return -1 as a 32-bit value in a 64-bit SQLLEN field without proper sign extension.

// Skip this test on 32-bit platforms as the issue only occurs on 64-bit
if (IntPtr.Size != 8)
{
return;
}

// Load the SQLLEN type using reflection since it's internal
Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

// Create a SQLLEN instance with IntPtr constructor
// This simulates what happens when ODBC returns 0x00000000FFFFFFFF
ConstructorInfo intPtrConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(IntPtr) },
null);
Assert.NotNull(intPtrConstructor);

// Create IntPtr with value 0x00000000FFFFFFFF (4294967295 as unsigned, -1 as signed 32-bit)
IntPtr problematicValue = new IntPtr(0x00000000FFFFFFFF);
object sqllenInstance = intPtrConstructor.Invoke(new object[] { problematicValue });

// Get the implicit conversion operator to int
MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);
Assert.Equal(typeof(int), implicitToInt.ReturnType);

// This should not throw OverflowException and should return -1
int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(-1, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_HandlesNormalValues()
{
// Verify that normal values still work correctly
Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

// Test with value 100
ConstructorInfo intConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(int) },
null);
Assert.NotNull(intConstructor);

object sqllenInstance = intConstructor.Invoke(new object[] { 100 });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(100, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_HandlesZero()
{
Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

ConstructorInfo intConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(int) },
null);
Assert.NotNull(intConstructor);

object sqllenInstance = intConstructor.Invoke(new object[] { 0 });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(0, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_HandlesNegativeValues()
{
Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

ConstructorInfo intConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(int) },
null);
Assert.NotNull(intConstructor);

object sqllenInstance = intConstructor.Invoke(new object[] { -42 });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(-42, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_HandlesSQLNTS_64Bit()
{
// Test handling of SQL_NTS (-3) when improperly represented as 0x00000000FFFFFFFD
if (IntPtr.Size != 8)
{
return;
}

Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

ConstructorInfo intPtrConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(IntPtr) },
null);
Assert.NotNull(intPtrConstructor);

IntPtr problematicValue = new IntPtr(0x00000000FFFFFFFD);
object sqllenInstance = intPtrConstructor.Invoke(new object[] { problematicValue });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(-3, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_HandlesSQLNO_TOTAL_64Bit()
{
// Test handling of SQL_NO_TOTAL (-4) when improperly represented as 0x00000000FFFFFFFC
if (IntPtr.Size != 8)
{
return;
}

Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

ConstructorInfo intPtrConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(IntPtr) },
null);
Assert.NotNull(intPtrConstructor);

IntPtr problematicValue = new IntPtr(0x00000000FFFFFFFC);
object sqllenInstance = intPtrConstructor.Invoke(new object[] { problematicValue });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

int result = (int)implicitToInt.Invoke(null, new[] { sqllenInstance });
Assert.Equal(-4, result);
}

[Fact]
public void SQLLEN_ImplicitConversion_ThrowsForOutOfRangeValues_64Bit()
{
// Test that truly out-of-range values still throw OverflowException
if (IntPtr.Size != 8)
{
return;
}

Type sqllenType = typeof(OdbcConnection).Assembly.GetType("System.Data.Odbc.SQLLEN");
Assert.NotNull(sqllenType);

ConstructorInfo intPtrConstructor = sqllenType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(IntPtr) },
null);
Assert.NotNull(intPtrConstructor);

// Test a value that's clearly too large: 0x0000000100000000 (4294967296)
IntPtr tooLargeValue = new IntPtr(0x0000000100000000);
object sqllenInstance = intPtrConstructor.Invoke(new object[] { tooLargeValue });

MethodInfo implicitToInt = sqllenType.GetMethod(
"op_Implicit",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { sqllenType },
null);
Assert.NotNull(implicitToInt);

// This should throw OverflowException
var ex = Assert.Throws<TargetInvocationException>(() =>
implicitToInt.Invoke(null, new[] { sqllenInstance }));
Assert.IsType<OverflowException>(ex.InnerException);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="TestCommon\DataTestUtility.cs" />
<Compile Include="TestCommon\CheckConnStrSetupFactAttribute.cs" />
<Compile Include="OdbcParameterTests.cs" />
<Compile Include="SQLLENTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix'">
<Compile Include="$(CommonPath)Interop\Unix\Interop.Odbc.cs"
Expand Down
Loading