Skip to content

Support VECTOR data type #1551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion .ci/config/config.compression+ssl.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin,Vector",
"MySqlBulkLoaderLocalCsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.TSV",
"CertificatesPath": "../../../../.ci/server/certs"
2 changes: 1 addition & 1 deletion .ci/config/config.compression.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,ZeroDateTime",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,Vector,ZeroDateTime",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
}
2 changes: 1 addition & 1 deletion .ci/config/config.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,ZeroDateTime",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,Vector,ZeroDateTime",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
}
2 changes: 1 addition & 1 deletion .ci/config/config.ssl.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin,Vector",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV",
"CertificatesPath": "../../../../.ci/server/certs"
2 changes: 1 addition & 1 deletion .ci/docker-run.sh
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ MYSQL=mysql
if [[ "$IMAGE" == mariadb* ]]; then
MYSQL_EXTRA='--in-predicate-conversion-threshold=100000 --plugin-maturity=beta'
fi
if [ "$IMAGE" == "mariadb:11.4" ] || [ "$IMAGE" == "mariadb:11.6" ]; then
if [ "$IMAGE" == "mariadb:11.4" ] || [ "$IMAGE" == "mariadb:11.7" ]; then
MYSQL='mariadb'
fi

22 changes: 11 additions & 11 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ jobs:
arguments: 'tests\IntegrationTests\IntegrationTests.csproj -c MySqlData'
testRunTitle: 'MySql.Data integration tests'
env:
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,TlsFingerprintValidation,UnixDomainSocket'
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,TlsFingerprintValidation,UnixDomainSocket,Vector'
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=root;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600'
DATA__CERTIFICATESPATH: '$(Build.Repository.LocalPath)\.ci\server\certs\'
DATA__MYSQLBULKLOADERLOCALCSVFILE: '$(Build.Repository.LocalPath)\tests\TestData\LoadData_UTF8_BOM_Unix.CSV'
@@ -120,7 +120,7 @@ jobs:
arguments: '-c Release --no-restore -p:TestTfmsInParallel=false'
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net481/net9.0', 'No SSL') }}
env:
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket'
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,Vector'
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True;UseCompression=True'

- job: windows_integration_tests_2
@@ -158,7 +158,7 @@ jobs:
arguments: '-c Release --no-restore -p:TestTfmsInParallel=false'
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net8.0', 'No SSL') }}
env:
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket'
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,Vector'
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True'

- job: linux_integration_tests
@@ -171,31 +171,31 @@ jobs:
'MySQL 8.0':
image: 'mysql:8.0'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,Vector,ZeroDateTime'
'MySQL 8.4':
image: 'mysql:8.4'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,Vector,ZeroDateTime'
'MySQL 9.3':
image: 'mysql:9.3'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
'MariaDB 10.6':
image: 'mariadb:10.6'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin,Vector'
'MariaDB 10.11':
image: 'mariadb:10.11'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin,Vector'
'MariaDB 11.4':
image: 'mariadb:11.4'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin,Redirection'
'MariaDB 11.6':
image: 'mariadb:11.6'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,UuidToBin,Vector'
'MariaDB 11.7':
image: 'mariadb:11.7'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,UuidToBin,VectorType'
steps:
- template: '.ci/integration-tests-steps.yml'
parameters:
2 changes: 1 addition & 1 deletion docs/content/home.md
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ Server | Versions | Notes
Amazon Aurora RDS | 2.x, 3.x | Use `Pipelining=False` [for Aurora 2.x](https://mysqlconnector.net/troubleshooting/aurora-freeze/)
Azure Database for MySQL | 5.7, 8.0 | Single Server and Flexible Server
Google Cloud SQL for MySQL | 5.6, 5.7, 8.0 |
MariaDB | 10.x (**10.6**, **10.11**), 11.x (**11.4**, **11.6**) |
MariaDB | 10.x (**10.6**, **10.11**), 11.x (**11.4**, **11.7**) |
MySQL | 5.5, 5.6, 5.7, 8.x (**8.0**, **8.4**), 9.x (**9.3**) | 5.5 is EOL and has some [compatibility issues](https://github.com/mysql-net/MySqlConnector/issues/1192); 5.6 and 5.7 are EOL
Percona Server | 5.6, 5.7, 8.0 |
PlanetScale | | See PlanetScale [MySQL compatibility notes](https://planetscale.com/docs/reference/mysql-compatibility)
1 change: 1 addition & 0 deletions docs/content/troubleshooting/parameter-types.md
Original file line number Diff line number Diff line change
@@ -39,5 +39,6 @@ In some cases, this may be as simple as calling `.ToString()` or `.ToString(Cult
* .NET primitives: `bool`, `byte`, `char`, `double`, `float`, `int`, `long`, `sbyte`, `short`, `uint`, `ulong`, `ushort`
* Common types: `BigInteger`, `DateOnly`, `DateTime`, `DateTimeOffset`, `decimal`, `enum`, `Guid`, `string`, `TimeOnly`, `TimeSpan`
* BLOB types: `ArraySegment<byte>`, `byte[]`, `Memory<byte>`, `ReadOnlyMemory<byte>`
* Vector types: `float[]`, `Memory<float>`, `ReadOnlyMemory<float>`
* String types: `Memory<char>`, `ReadOnlyMemory<char>`, `StringBuilder`
* Custom MySQL types: `MySqlDateTime`, `MySqlDecimal`, `MySqlGeometry`
3 changes: 3 additions & 0 deletions src/MySqlConnector/ColumnReaders/ColumnReader.cs
Original file line number Diff line number Diff line change
@@ -113,6 +113,9 @@ public static ColumnReader Create(bool isBinary, ColumnDefinitionPayload columnD
case ColumnType.Null:
return NullColumnReader.Instance;

case ColumnType.Vector:
return VectorColumnReader.Instance;

default:
throw new NotImplementedException($"Reading {columnDefinition.ColumnType} not implemented");
}
12 changes: 12 additions & 0 deletions src/MySqlConnector/ColumnReaders/VectorColumnReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;

namespace MySqlConnector.ColumnReaders;

internal sealed class VectorColumnReader : ColumnReader
{
public static VectorColumnReader Instance { get; } = new();

public override object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition) =>
new ReadOnlyMemory<float>(MemoryMarshal.Cast<byte, float>(data).ToArray());
}
2 changes: 1 addition & 1 deletion src/MySqlConnector/Core/Row.cs
Original file line number Diff line number Diff line change
@@ -455,7 +455,7 @@ private void CheckBinaryColumn(int ordinal)
if ((column.ColumnFlags & ColumnFlags.Binary) == 0 ||
(columnType != ColumnType.String && columnType != ColumnType.VarString && columnType != ColumnType.TinyBlob &&
columnType != ColumnType.Blob && columnType != ColumnType.MediumBlob && columnType != ColumnType.LongBlob &&
columnType != ColumnType.Geometry))
columnType != ColumnType.Geometry && columnType != ColumnType.Vector))
{
throw new InvalidCastException($"Can't convert {columnType} to bytes.");
}
4 changes: 4 additions & 0 deletions src/MySqlConnector/Core/SingleCommandPayloadCreator.cs
Original file line number Diff line number Diff line change
@@ -165,6 +165,10 @@ private static void WriteBinaryParameters(ByteBufferWriter writer, MySqlParamete
mySqlDbType = TypeMapper.Instance.GetMySqlDbTypeForDbType(dbType);
}

// HACK: MariaDB doesn't have a dedicated Vector type so mark it as binary data
if (mySqlDbType == MySqlDbType.Vector && command.Connection!.Session.ServerVersion.IsMariaDb)
mySqlDbType = MySqlDbType.LongBlob;

writer.Write(TypeMapper.ConvertToColumnTypeAndFlags(mySqlDbType, command.Connection!.GuidFormat));

if (supportsQueryAttributes)
8 changes: 8 additions & 0 deletions src/MySqlConnector/Core/TypeMapper.cs
Original file line number Diff line number Diff line change
@@ -55,6 +55,10 @@ private TypeMapper()
AddColumnTypeMetadata(new("DOUBLE", typeDouble, MySqlDbType.Double));
AddColumnTypeMetadata(new("FLOAT", typeFloat, MySqlDbType.Float));

// vector
var typeFloatReadOnlyMemory = AddDbTypeMapping(new(typeof(ReadOnlyMemory<float>), [DbType.Object]));
AddColumnTypeMetadata(new("VECTOR", typeFloatReadOnlyMemory, MySqlDbType.Vector, binary: true, simpleDataTypeName: "VECTOR", createFormat: "VECTOR({0})"));

// string
var typeFixedString = AddDbTypeMapping(new(typeof(string), [DbType.StringFixedLength, DbType.AnsiStringFixedLength], convert: Convert.ToString!));
var typeString = AddDbTypeMapping(new(typeof(string), [DbType.String, DbType.AnsiString, DbType.Xml], convert: Convert.ToString!));
@@ -311,6 +315,9 @@ public static MySqlDbType ConvertToMySqlDbType(ColumnDefinitionPayload columnDef
case ColumnType.Set:
return MySqlDbType.Set;

case ColumnType.Vector:
return MySqlDbType.Vector;

default:
throw new NotImplementedException($"ConvertToMySqlDbType for {columnDefinition.ColumnType} is not implemented");
}
@@ -347,6 +354,7 @@ public static ushort ConvertToColumnTypeAndFlags(MySqlDbType dbType, MySqlGuidFo
MySqlDbType.NewDecimal => ColumnType.NewDecimal,
MySqlDbType.Geometry => ColumnType.Geometry,
MySqlDbType.Null => ColumnType.Null,
MySqlDbType.Vector => ColumnType.Vector,
_ => throw new NotImplementedException($"ConvertToColumnTypeAndFlags for {dbType} is not implemented"),
};
return (ushort) ((byte) columnType | (isUnsigned ? 0x8000 : 0));
2 changes: 1 addition & 1 deletion src/MySqlConnector/MySqlDataReader.cs
Original file line number Diff line number Diff line change
@@ -671,7 +671,7 @@ private static async Task ReadOutParametersAsync(IMySqlCommand command, ResultSe
if (param.HasSetDbType && !row.IsDBNull(columnIndex))
{
var dbTypeMapping = TypeMapper.Instance.GetDbTypeMapping(param.DbType);
if (dbTypeMapping is not null)
if (dbTypeMapping is not null && param.DbType is not DbType.Object)
{
param.Value = dbTypeMapping.DoConversion(row.GetValue(columnIndex));
continue;
1 change: 1 addition & 0 deletions src/MySqlConnector/MySqlDbColumn.cs
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ internal MySqlDbColumn(int ordinal, ColumnDefinitionPayload column, bool allowZe
var type = columnTypeMetadata.DbTypeMapping.ClrType;
var columnSize = type == typeof(string) || type == typeof(Guid) ?
column.ColumnLength / ProtocolUtility.GetBytesPerCharacter(column.CharacterSet) :
column.ColumnType == ColumnType.Vector ? column.ColumnLength / 4 :
column.ColumnLength;

AllowDBNull = (column.ColumnFlags & ColumnFlags.NotNull) == 0;
1 change: 1 addition & 0 deletions src/MySqlConnector/MySqlDbType.cs
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ public enum MySqlDbType
VarChar,
String,
Geometry,
Vector = 242,
UByte = 501,
UInt16,
UInt32,
21 changes: 20 additions & 1 deletion src/MySqlConnector/MySqlParameter.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
#if NET8_0_OR_GREATER
using System.Text.Unicode;
@@ -284,7 +285,7 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
{
writer.WriteString(ulongValue);
}
else if (Value is byte[] or ReadOnlyMemory<byte> or Memory<byte> or ArraySegment<byte> or MySqlGeometry or MemoryStream)
else if (Value is byte[] or ReadOnlyMemory<byte> or Memory<byte> or ArraySegment<byte> or MySqlGeometry or MemoryStream or float[] or ReadOnlyMemory<float> or Memory<float>)
{
var inputSpan = Value switch
{
@@ -293,6 +294,9 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
Memory<byte> memory => memory.Span,
MySqlGeometry geometry => geometry.ValueSpan,
MemoryStream memoryStream => memoryStream.TryGetBuffer(out var streamBuffer) ? streamBuffer.AsSpan() : memoryStream.ToArray().AsSpan(),
float[] floatArray => MemoryMarshal.AsBytes(floatArray.AsSpan()),
Memory<float> memory => MemoryMarshal.AsBytes(memory.Span),
ReadOnlyMemory<float> memory => MemoryMarshal.AsBytes(memory.Span),
_ => ((ReadOnlyMemory<byte>) Value).Span,
};

@@ -731,6 +735,21 @@ private void AppendBinary(ByteBufferWriter writer, object value, StatementPrepar
{
writer.Write(unchecked((ulong) BitConverter.DoubleToInt64Bits(doubleValue)));
}
else if (value is float[] floatArrayValue)
{
writer.WriteLengthEncodedInteger(unchecked((ulong) floatArrayValue.Length * 4));
writer.Write(MemoryMarshal.AsBytes(floatArrayValue.AsSpan()));
}
else if (value is Memory<float> floatMemory)
{
writer.WriteLengthEncodedInteger(unchecked((ulong) floatMemory.Length * 4));
writer.Write(MemoryMarshal.AsBytes(floatMemory.Span));
}
else if (value is ReadOnlyMemory<float> floatReadOnlyMemory)
{
writer.WriteLengthEncodedInteger(unchecked((ulong) floatReadOnlyMemory.Length * 4));
writer.Write(MemoryMarshal.AsBytes(floatReadOnlyMemory.Span));
}
else if (value is decimal decimalValue)
{
writer.WriteLengthEncodedAsciiString(decimalValue.ToString(CultureInfo.InvariantCulture));
1 change: 1 addition & 0 deletions src/MySqlConnector/Protocol/ColumnType.cs
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ internal enum ColumnType
Bit = 16,
Timestamp2 = 17,
DateTime2 = 18,
Vector = 242,
Json = 0xF5,
NewDecimal = 0xF6,
Enum = 0xF7,
2 changes: 1 addition & 1 deletion tests/IntegrationTests/CharacterSetTests.cs
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@ public void CollationConnection(bool reopenConnection)

var collation = connection.Query<string>(@"select @@collation_connection;").Single();
var expected = connection.ServerVersion.Substring(0, 2) is "8." or "9." ? "utf8mb4_0900_ai_ci" :
connection.ServerVersion.StartsWith("11.4.", StringComparison.Ordinal) || connection.ServerVersion.StartsWith("11.6.", StringComparison.Ordinal) ? "utf8mb4_uca1400_ai_ci" :
connection.ServerVersion.StartsWith("11.4.", StringComparison.Ordinal) || connection.ServerVersion.StartsWith("11.7.", StringComparison.Ordinal) ? "utf8mb4_uca1400_ai_ci" :
"utf8mb4_general_ci";
Assert.Equal(expected, collation);
}
Loading