Skip to content

Commit

Permalink
Add MariaDB capabilities support
Browse files Browse the repository at this point in the history
  • Loading branch information
mirromutth authored and jchrys committed Jan 8, 2024
1 parent 7bbe8af commit a605469
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 61 deletions.
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

0 comments on commit a605469

Please sign in to comment.