Skip to content
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

Add MariaDB capabilities support #187

Merged
merged 1 commit into from
Jan 9, 2024
Merged
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
122 changes: 75 additions & 47 deletions src/main/java/io/asyncer/r2dbc/mysql/Capability.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,81 +25,80 @@
public final class Capability {

/**
* Can use long password.
* <p>
* TODO: Reinterpret it as {@code CLIENT_MYSQL} to support MariaDB 10.2 and above.
* If UNSET, the server supports the MariaDB protocol and statements.
*/
private static final int LONG_PASSWORD = 1;
private static final long CLIENT_MYSQL = 1L;

/**
* Use found/touched rows instead of changed rows for affected rows. Should enable it by default.
*/
private static final int FOUND_ROWS = 2;
private static final long FOUND_ROWS = 2L;

/**
* Use 2-bytes definition flags of {@code DefinitionMetadataMessage}.
* <p>
* Very old servers (before 3.23) will not set this capability flag.
*/
private static final int LONG_FLAG = 4;
private static final long LONG_FLAG = 4L;

/**
* Connect to server with a database.
*/
private static final int CONNECT_WITH_DB = 8;
private static final long CONNECT_WITH_DB = 8L;

/**
* Enable it to disallow a statement which use {@code database.table.column} for access other schema
* data.
*/
private static final int NO_SCHEMA = 16;
private static final long NO_SCHEMA = 16L;

/**
* The deflate compression, old compression flag.
*/
private static final int COMPRESS = 32;
private static final long COMPRESS = 32L;

// private static final int ODBC = 64; // R2DBC driver is not ODBC driver.
// private static final long ODBC = 64L; // R2DBC driver is not ODBC driver.

/**
* Allow to use LOAD DATA [LOCAL] INFILE statement.
*/
private static final int LOCAL_FILES = 128;
private static final long LOCAL_FILES = 128L;

/**
* Ignore space between a built-in function name and the subsequent parenthesis.
* <p>
* Note: Ignoring spaces may cause ambiguity.
* <p>
* See also https://dev.mysql.com/doc/refman/8.0/en/function-resolution.html .
* See also <a href="https://dev.mysql.com/doc/refman/8.0/en/function-resolution.html">
* Function Resolution</a>.
*/
private static final int IGNORE_SPACE = 256;
private static final long IGNORE_SPACE = 256L;

/**
* The protocol version is 4.1 (instead of 3.20).
*/
private static final int PROTOCOL_41 = 512;
private static final long PROTOCOL_41 = 512L;

/**
* Use {@code wait_interactive_timeout} instead of {@code wait_timeout} for the server waits for activity
* on a connection before closing it.
*/
private static final int INTERACTIVE = 1024;
private static final long INTERACTIVE = 1024L;

/**
* Enable SSL.
*/
private static final int SSL = 2048;
private static final long SSL = 2048L;

// private static final int IGNORE_SIGPIPE = 4096; // Connector/C only flag.
// private static final long IGNORE_SIGPIPE = 4096L; // Connector/C only flag.

/**
* Allow transactions. All available versions of MySQL server support it.
*/
private static final int TRANSACTIONS = 8192;
private static final long TRANSACTIONS = 8192L;

// Old flag and alias of PROTOCOL_41. It will not be used by any available server version/edition.
// private static final int RESERVED = 16384;
// private static final int RESERVED = 16384L;

/**
* Allow second part of authentication hashing salt.
Expand All @@ -108,67 +107,77 @@ public final class Capability {
* <p>
* Origin name: SECURE_CONNECTION.
*/
private static final int SECURE_SALT = 32768;
private static final long SECURE_SALT = 32768L;

/**
* Allow to send multiple statements in text query and prepare query.
* <p>
* Old name: MULTI_QUERIES.
*/
private static final int MULTI_STATEMENTS = 65536;
private static final long MULTI_STATEMENTS = 65536L;

/**
* Allow to receive multiple results in the response of executing a text query.
*/
private static final int MULTI_RESULTS = 1 << 17;
private static final long MULTI_RESULTS = 1L << 17;

/**
* Allow to receive multiple results in the response of executing a prepare query.
*/
private static final int PS_MULTI_RESULTS = 1 << 18;
private static final long PS_MULTI_RESULTS = 1L << 18;

/**
* Supports authentication plugins. Server will send more details (i.e. name) for authentication plugin.
*/
private static final int PLUGIN_AUTH = 1 << 19;
private static final long PLUGIN_AUTH = 1L << 19;

/**
* Connection attributes should be sent.
*/
private static final int CONNECT_ATTRS = 1 << 20;
private static final long CONNECT_ATTRS = 1L << 20;

/**
* Can use var-integer sized bytes to encode client authentication.
* <p>
* Origin name: PLUGIN_AUTH_LENENC_CLIENT_DATA.
*/
private static final int VAR_INT_SIZED_AUTH = 1 << 21;
private static final long VAR_INT_SIZED_AUTH = 1L << 21;

// private static final int HANDLE_EXPIRED_PASSWORD = 1 << 22; // Client can handle expired passwords.
// private static final int SESSION_TRACK = 1 << 23;
// private static final long HANDLE_EXPIRED_PASSWORD = 1L << 22; // Client can handle expired passwords.
// private static final long SESSION_TRACK = 1L << 23;

/**
* The MySQL server marks the EOF message as deprecated and use OK message instead.
*/
private static final int DEPRECATE_EOF = 1 << 24;
private static final long DEPRECATE_EOF = 1L << 24;

// Allow the server not to send column metadata in result set,
// should NEVER enable this option.
// private static final int OPTIONAL_RESULT_SET_METADATA = 1 << 25;
// private static final int Z_STD_COMPRESSION = 1 << 26;
// private static final long OPTIONAL_RESULT_SET_METADATA = 1L << 25;
// private static final long Z_STD_COMPRESSION = 1L << 26;

// A reserved flag, used to extend the 32-bits capability bitmap to 64-bits.
// There is no available MySql server version/edition to support it.
// private static final int CAPABILITY_EXTENSION = 1 << 29;
// private static final int SSL_VERIFY_SERVER_CERT = 1 << 30; // Client only flag, use SslMode instead.
// private static final int REMEMBER_OPTIONS = 1 << 31; // Connector/C only flag.
// private static final long CAPABILITY_EXTENSION = 1L << 29;
// private static final long SSL_VERIFY_SERVER_CERT = 1L << 30; // Client only flag, use SslMode instead.
// private static final long REMEMBER_OPTIONS = 1L << 31; // Connector/C only flag.

// private static final long MARIADB_CLIENT_PROGRESS = 1L << 32;
// private static final long MARIADB_CLIENT_COM_MULTI = 1L << 33;
// private static final long MARIADB_CLIENT_STMT_BULK_OPERATIONS = 1L << 34;
// private static final long MARIADB_CLIENT_EXTENDED_TYPE_INFO = 1L << 35;
// private static final long MARIADB_CLIENT_CACHE_METADATA = 1L << 36;

private static final int ALL_SUPPORTED = LONG_PASSWORD | FOUND_ROWS | LONG_FLAG | CONNECT_WITH_DB |
private static final long ALL_SUPPORTED = CLIENT_MYSQL | FOUND_ROWS | LONG_FLAG | CONNECT_WITH_DB |
NO_SCHEMA | COMPRESS | LOCAL_FILES | IGNORE_SPACE | PROTOCOL_41 | INTERACTIVE | SSL |
TRANSACTIONS | SECURE_SALT | MULTI_STATEMENTS | MULTI_RESULTS | PS_MULTI_RESULTS |
PLUGIN_AUTH | CONNECT_ATTRS | VAR_INT_SIZED_AUTH | DEPRECATE_EOF;

private final int bitmap;
private final long bitmap;

public boolean isMariaDb() {
return (bitmap & CLIENT_MYSQL) == 0;
}

/**
* Checks if the connection will be connected and logon with a database.
Expand Down Expand Up @@ -261,12 +270,31 @@ public boolean isTransactionAllowed() {
}

/**
* Get the original bitmap of {@link Capability this}.
* Extends MariaDB capabilities.
*
* @param hiCapabilities the bitmap of extend capabilities.
* @return a new {@link Capability} takes base and extend capabilities.
*/
public Capability extendMariaDb(long hiCapabilities) {
return of((this.bitmap & 0xFFFFFFFFL) | (hiCapabilities << 32));
}

/**
* Get the lower 32-bits bitmap of {@link Capability this}.
*
* @return the lower 32-bits bitmap.
*/
public int getBaseBitmap() {
return (int) bitmap;
}

/**
* Get the higher 32-bits bitmap of {@link Capability this}.
*
* @return the bitmap.
* @return the higher 32-bits bitmap.
*/
public int getBitmap() {
return bitmap;
public int getExtendBitmap() {
return (int) (bitmap >>> 32);
}

@Override
Expand All @@ -285,36 +313,36 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return bitmap;
return Long.hashCode(bitmap);
}

@Override
public String toString() {
// Do not consider complex output, just use hex.
return "Capability<0x" + Integer.toHexString(bitmap) + '>';
return "Capability<0x" + Long.toHexString(bitmap) + '>';
}

Builder mutate() {
return new Builder(bitmap);
}

private Capability(int bitmap) {
private Capability(long bitmap) {
this.bitmap = bitmap;
}

/**
* Creates a {@link Capability} with capabilities bitmap. It will unset all unknown flags.
*
* @param capabilities the capabilities bitmap.
* @param capabilities the bitmap of capabilities.
* @return the {@link Capability} without unknown flags.
*/
public static Capability of(int capabilities) {
public static Capability of(long capabilities) {
return new Capability(capabilities & ALL_SUPPORTED);
}

static final class Builder {

private int bitmap;
private long bitmap;

void disableConnectWithDatabase() {
this.bitmap &= ~CONNECT_WITH_DB;
Expand Down Expand Up @@ -352,7 +380,7 @@ Capability build() {
return of(this.bitmap);
}

private Builder(int bitmap) {
private Builder(long bitmap) {
this.bitmap = bitmap;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ protected int size() {
@Override
protected void writeTo(ByteBuf buf) {
// Protocol 3.20 only allows low 16-bits capabilities.
buf.writeShortLE(capability.getBitmap() & 0xFFFF)
buf.writeShortLE(capability.getBaseBitmap() & 0xFFFF)
.writeMediumLE(Envelopes.MAX_ENVELOPE_SIZE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
*/
final class SslRequest41 extends SizedClientMessage implements SslRequest {

private static final int FILTER_SIZE = 23;
private static final int RESERVED_SIZE = 19;

private static final int BUF_SIZE = Integer.BYTES + Integer.BYTES + Byte.BYTES + FILTER_SIZE;
private static final int MARIA_DB_CAPABILITY_SIZE = Integer.BYTES;

private static final int BUF_SIZE = Integer.BYTES + Integer.BYTES + Byte.BYTES +
RESERVED_SIZE + MARIA_DB_CAPABILITY_SIZE;

private final int envelopeId;

Expand Down Expand Up @@ -91,10 +94,16 @@ protected int size() {

@Override
protected void writeTo(ByteBuf buf) {
buf.writeIntLE(capability.getBitmap())
buf.writeIntLE(capability.getBaseBitmap())
.writeIntLE(Envelopes.MAX_ENVELOPE_SIZE)
.writeByte(collationId & 0xFF) // only low 8-bits
.writeZero(FILTER_SIZE);
.writeByte(collationId & 0xFF); // only low 8-bits

if (capability.isMariaDb()) {
buf.writeZero(RESERVED_SIZE)
.writeIntLE(capability.getExtendBitmap());
} else {
buf.writeZero(RESERVED_SIZE + MARIA_DB_CAPABILITY_SIZE);
}
}

int getCollationId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
*/
final class HandshakeV10Request implements HandshakeRequest, ServerStatusMessage {

private static final int RESERVED_SIZE = 10;
private static final int RESERVED_SIZE = 6;

private static final int MARIA_DB_CAPABILITY_SIZE = Integer.BYTES;

private static final int SALT_FIRST_PART_SIZE = 8;

Expand Down Expand Up @@ -133,23 +135,26 @@ static HandshakeV10Request decode(int envelopeId, ByteBuf buf, HandshakeHeader h
buf.skipBytes(SALT_FIRST_PART_SIZE + 1);

// The Server Capabilities first part following the salt first part. (always lower 2-bytes)
int loCapabilities = buf.readUnsignedShortLE();
long loCapabilities = buf.readUnsignedShortLE();

// MySQL is using 16 bytes to identify server character. There has lower 8-bits only, skip it.
buf.skipBytes(1);
builder.serverStatuses(buf.readShortLE());

// The Server Capabilities second part following the server statuses. (always upper 2-bytes)
int hiCapabilities = buf.readUnsignedShortLE() << Short.SIZE;
Capability capability = Capability.of(loCapabilities | hiCapabilities);

builder.serverCapability(capability);
long miCapabilities = ((long) buf.readUnsignedShortLE()) << Short.SIZE;
Capability capability = Capability.of(loCapabilities | miCapabilities);

// If PLUGIN_AUTH flag not exists, MySQL server will return 0x00 always.
short saltSize = buf.readUnsignedByte();

// Reserved field, all bytes are 0x00.
buf.skipBytes(RESERVED_SIZE);
if (capability.isMariaDb()) {
buf.skipBytes(RESERVED_SIZE);
builder.serverCapability(capability.extendMariaDb(buf.readUnsignedIntLE()));
} else {
buf.skipBytes(RESERVED_SIZE + MARIA_DB_CAPABILITY_SIZE);
builder.serverCapability(capability);
}

if (capability.isSaltSecured()) {
// If it has not this part, means it is using mysql_old_password,
Expand Down