Skip to content

Commit

Permalink
[CONJ-1117] adding new value returnMultiValuesGeneratedIds for conn…
Browse files Browse the repository at this point in the history
…ector 2.x compatibility, so getGeneratedKeys() return all ids of multi-value inserts
  • Loading branch information
rusher committed Dec 14, 2023
1 parent 6deda54 commit be848da
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 14 deletions.
12 changes: 10 additions & 2 deletions src/main/java/org/mariadb/jdbc/BasePreparedStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,20 @@ protected void checkIfInsertCommand() {
if (sql == null) {
isCommandInsert = false;
} else {
ClientParser parser = ClientParser.parameterParts(sql, (con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0);
isCommandInsert = parser.isInsert() && !parser.isInsertDuplicate();
ClientParser parser =
ClientParser.parameterParts(
sql, (con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0);
isInsertDuplicate = parser.isInsertDuplicate();
isCommandInsert = parser.isInsert() && !isInsertDuplicate;
}
}
}

@Override
public String getLastSql() {
return sql;
}

/**
* Set PREPARE result
*
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/mariadb/jdbc/ClientPreparedStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public ClientPreparedStatement(
boolean noBackslashEscapes =
(con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0;
parser = ClientParser.parameterParts(sql, noBackslashEscapes);
isCommandInsert = parser.isInsert() && !parser.isInsertDuplicate();
isInsertDuplicate = parser.isInsertDuplicate();
isCommandInsert = parser.isInsert() && !isInsertDuplicate;
parameters = new ParameterList(parser.getParamCount());
}

Expand Down
29 changes: 29 additions & 0 deletions src/main/java/org/mariadb/jdbc/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class Configuration {
private CatalogTerm useCatalogTerm = CatalogTerm.UseCatalog;
private boolean createDatabaseIfNotExist = false;
private boolean useLocalSessionState = false;
private boolean returnMultiValuesGeneratedIds = false;
private TransactionIsolation transactionIsolation = null;
private int defaultFetchSize = 0;
private int maxQuerySizeToLog = 1024;
Expand Down Expand Up @@ -169,6 +170,7 @@ private Configuration(
CatalogTerm useCatalogTerm,
boolean createDatabaseIfNotExist,
boolean useLocalSessionState,
boolean returnMultiValuesGeneratedIds,
TransactionIsolation transactionIsolation,
int defaultFetchSize,
int maxQuerySizeToLog,
Expand Down Expand Up @@ -245,6 +247,7 @@ private Configuration(
this.useMysqlMetadata = useMysqlMetadata;
this.useCatalogTerm = useCatalogTerm;
this.createDatabaseIfNotExist = createDatabaseIfNotExist;
this.returnMultiValuesGeneratedIds = returnMultiValuesGeneratedIds;
this.useLocalSessionState = useLocalSessionState;
this.transactionIsolation = transactionIsolation;
this.defaultFetchSize = defaultFetchSize;
Expand Down Expand Up @@ -359,6 +362,7 @@ private Configuration(
String useCatalogTerm,
Boolean createDatabaseIfNotExist,
Boolean useLocalSessionState,
Boolean returnMultiValuesGeneratedIds,
Boolean includeInnodbStatusInDeadlockExceptions,
Boolean includeThreadDumpInDeadlockExceptions,
String servicePrincipalName,
Expand Down Expand Up @@ -456,6 +460,8 @@ private Configuration(
}
if (createDatabaseIfNotExist != null) this.createDatabaseIfNotExist = createDatabaseIfNotExist;
if (useLocalSessionState != null) this.useLocalSessionState = useLocalSessionState;
if (returnMultiValuesGeneratedIds != null)
this.returnMultiValuesGeneratedIds = returnMultiValuesGeneratedIds;
if (includeInnodbStatusInDeadlockExceptions != null)
this.includeInnodbStatusInDeadlockExceptions = includeInnodbStatusInDeadlockExceptions;
if (includeThreadDumpInDeadlockExceptions != null)
Expand Down Expand Up @@ -1040,6 +1046,7 @@ public Configuration clone(String username, String password) {
this.useCatalogTerm,
this.createDatabaseIfNotExist,
this.useLocalSessionState,
this.returnMultiValuesGeneratedIds,
this.transactionIsolation,
this.defaultFetchSize,
this.maxQuerySizeToLog,
Expand Down Expand Up @@ -1581,6 +1588,15 @@ public boolean useLocalSessionState() {
return useLocalSessionState;
}

/**
* Returns multi-values generated ids. For mariadb 2.x connector compatibility
*
* @return must returns multi-values generated ids.
*/
public boolean returnMultiValuesGeneratedIds() {
return returnMultiValuesGeneratedIds;
}

/**
* On deadlock exception, must driver execute additional commands to show innodb status in error
* description.
Expand Down Expand Up @@ -1890,6 +1906,7 @@ public static final class Builder implements Cloneable {
private String useCatalogTerm;
private Boolean createDatabaseIfNotExist;
private Boolean useLocalSessionState;
private Boolean returnMultiValuesGeneratedIds;
private Integer defaultFetchSize;
private Integer maxQuerySizeToLog;
private Integer maxAllowedPacket;
Expand Down Expand Up @@ -2620,6 +2637,17 @@ public Builder useLocalSessionState(Boolean useLocalSessionState) {
return this;
}

/**
* indicate if connector must return multi-generated ids. (For connector 2.x compatibility)
*
* @param returnMultiValuesGeneratedIds must return multi-values generated ids
* @return this {@link Builder}
*/
public Builder returnMultiValuesGeneratedIds(Boolean returnMultiValuesGeneratedIds) {
this.returnMultiValuesGeneratedIds = returnMultiValuesGeneratedIds;
return this;
}

/**
* On dead-lock exception must add innodb status in exception error message. If enabled, an
* additional command will be done to retrieve innodb status when dead-lock occurs.
Expand Down Expand Up @@ -2936,6 +2964,7 @@ public Configuration build() throws SQLException {
this.useCatalogTerm,
this.createDatabaseIfNotExist,
this.useLocalSessionState,
this.returnMultiValuesGeneratedIds,
this.includeInnodbStatusInDeadlockExceptions,
this.includeThreadDumpInDeadlockExceptions,
this.servicePrincipalName,
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/mariadb/jdbc/DatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,8 @@ public ResultSet getExportedKeys(String catalog, String schema, String table)
database,
conf.useCatalogTerm() == CatalogTerm.UseSchema);
sb.append(firstCondition ? " WHERE " : " AND ")
.append("KCU.REFERENCED_TABLE_NAME = ").append(escapeQuote(table));
.append("KCU.REFERENCED_TABLE_NAME = ")
.append(escapeQuote(table));
sb.append(" ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ");
return executeQuery(sb.toString());
}
Expand Down
76 changes: 69 additions & 7 deletions src/main/java/org/mariadb/jdbc/Statement.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.mariadb.jdbc.export.ExceptionFactory;
import org.mariadb.jdbc.message.client.QueryPacket;
import org.mariadb.jdbc.message.server.OkPacket;
import org.mariadb.jdbc.util.ClientParser;
import org.mariadb.jdbc.util.NativeSql;
import org.mariadb.jdbc.util.constants.ColumnFlags;
import org.mariadb.jdbc.util.constants.ServerStatus;
Expand Down Expand Up @@ -60,12 +61,16 @@ public class Statement implements java.sql.Statement {
/** connection */
protected final Connection con;

protected Boolean isInsertDuplicate = null;

/** required query timeout */
protected int queryTimeout;

/** maximum row number */
protected long maxRows;

protected String lastSql;

/** fetch size */
protected int fetchSize;

Expand Down Expand Up @@ -740,6 +745,7 @@ public int[] executeBatch() throws SQLException {
if (batchQueries == null || batchQueries.isEmpty()) return new int[0];
lock.lock();
try {
this.lastSql = batchQueries.get(0);
this.autoGeneratedKeys = java.sql.Statement.RETURN_GENERATED_KEYS;
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
Expand Down Expand Up @@ -901,16 +907,52 @@ public ResultSet getGeneratedKeys() throws SQLException {
}

List<String[]> insertIds = new ArrayList<>();
if (currResult instanceof OkPacket && ((OkPacket) currResult).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) currResult).getLastInsertId())});
}
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket && ((OkPacket) result).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())});
if (con.getContext().getConf().returnMultiValuesGeneratedIds()
&& !checkIfInsertDuplicateCommand(getLastSql())) {
// For compatibility with 2.x connector
long autoIncrement = con.getContext().getAutoIncrement();
if (currResult instanceof OkPacket) {
OkPacket ok = ((OkPacket) currResult);
if (ok.getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())});
if (ok.getAffectedRows() > 1) {
for (int i = 1; i < ok.getAffectedRows(); i++) {
insertIds.add(
new String[] {String.valueOf(ok.getLastInsertId() + i * autoIncrement)});
}
}
}
}
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket) {
OkPacket ok = ((OkPacket) result);
if (ok.getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())});
if (ok.getAffectedRows() > 1) {
for (int i = 1; i < ok.getAffectedRows(); i++) {
insertIds.add(
new String[] {String.valueOf(ok.getLastInsertId() + i * autoIncrement)});
}
}
}
}
}
}
} else {
// standard behavior
if (currResult instanceof OkPacket && ((OkPacket) currResult).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) currResult).getLastInsertId())});
}
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket && ((OkPacket) result).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())});
}
}
}
}

if (insertIds.isEmpty()) {
return new CompleteResult(
new ColumnDecoder[0], new byte[0][], con.getContext(), resultSetType);
Expand All @@ -926,6 +968,20 @@ public ResultSet getGeneratedKeys() throws SQLException {
resultSetType);
}

protected boolean checkIfInsertDuplicateCommand(String sql) {
if (isInsertDuplicate == null) {
if (sql == null) {
isInsertDuplicate = false;
} else {
ClientParser parser =
ClientParser.parameterParts(
sql, (con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0);
isInsertDuplicate = parser.isInsertDuplicate();
}
}
return isInsertDuplicate;
}

/**
* Executes the given SQL statement and signals the driver with the given flag about whether the
* auto-generated keys produced by this <code>Statement</code> object should be made available for
Expand Down Expand Up @@ -971,6 +1027,7 @@ private void executeInternal(String sql, int autoGeneratedKeys) throws SQLExcept
checkNotClosed();
lock.lock();
try {
this.lastSql = sql;
this.autoGeneratedKeys = autoGeneratedKeys;
String cmd = escapeTimeout(sql);
results =
Expand Down Expand Up @@ -1524,6 +1581,7 @@ public long[] executeLargeBatch() throws SQLException {
if (batchQueries == null || batchQueries.isEmpty()) return new long[0];
lock.lock();
try {
this.lastSql = batchQueries.get(0);
this.autoGeneratedKeys = java.sql.Statement.RETURN_GENERATED_KEYS;
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
Expand Down Expand Up @@ -1682,6 +1740,10 @@ public boolean isSimpleIdentifier(String identifier) {
&& identifierPattern.matcher(identifier).matches();
}

public String getLastSql() {
return lastSql;
}

/**
* Enquote utf8 value.
*
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/mariadb/jdbc/client/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ public interface Context {
*/
void setThreadId(long connectionId);

/**
* Get server current auto_increment value
*
* @return server auto increment
*/
Long getAutoIncrement();

/**
* Set server autoincrement value
*
* @param autoIncrement current server autoincrement value
*/
void setAutoIncrement(long autoIncrement);

/**
* Get connection initial seed
*
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/mariadb/jdbc/client/context/BaseContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class BaseContext implements Context {
/** Server status context */
protected int serverStatus;

private Long autoIncrement;

private long threadId;
private String charset;

Expand Down Expand Up @@ -190,6 +192,15 @@ public void setTreadsConnected(long threadsConnected) {
if (hostAddress != null) hostAddress.setThreadsConnected(threadsConnected);
}

@Override
public Long getAutoIncrement() {
return autoIncrement;
}

public void setAutoIncrement(long autoIncrement) {
this.autoIncrement = autoIncrement;
}

public String getCharset() {
return charset;
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/mariadb/jdbc/client/impl/StandardClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@ private void postConnectionQueries() throws SQLException {
}
throw exceptionFactory.create("Initialization command fail", "08000", sqlException);
}

if (conf.returnMultiValuesGeneratedIds()) {
ClientMessage query = new QueryPacket("SELECT @@auto_increment_increment");
List<Completion> res = execute(query, true);
ResultSet rs = (ResultSet) res.get(0);
if (rs.next()) {
context.setAutoIncrement(rs.getLong(1));
}
}
}
}

Expand Down Expand Up @@ -427,6 +436,14 @@ public String createSessionVariableQuery(String serverTz, Context context) {
"autocommit=" + ((conf.autocommit() == null || conf.autocommit()) ? "1" : "0"));
}

if (conf.returnMultiValuesGeneratedIds()
&& ((context.getVersion().isMariaDBServer()
&& (context.getVersion().versionGreaterOrEqual(10, 2, 2)))
|| context.getVersion().versionGreaterOrEqual(5, 7, 0))) {
sessionCommands.add(
"session_track_system_variables = CONCAT(@@global.session_track_system_variables,',auto_increment_increment')");
}

// add configured session variable if configured
if (conf.sessionVariables() != null) {
sessionCommands.add(Security.parseSessionVariables(conf.sessionVariables()));
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mariadb/jdbc/client/result/Result.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,34 @@ public abstract class Result implements ResultSet, Completion {
private final MutableInt fieldLength = new MutableInt(0);
private final boolean forceAlias;
private final byte[] nullBitmap;

/** data size */
protected int dataSize = 0;

/** rows */
protected byte[][] data;

/** mutable field index */
protected MutableInt fieldIndex = new MutableInt();

/** is fully loaded */
protected boolean loaded;

/** is an output parameter result-set */
protected boolean outputParameter;

/** current row pointer */
protected int rowPointer = -1;

/** is result-set closed */
protected boolean closed;

/** statement that initiate this result */
protected Statement statement;

/** row number limit */
protected long maxRows;

private boolean closeOnCompletion;
private Map<String, Integer> mapper = null;
private int fetchSize;
Expand Down

0 comments on commit be848da

Please sign in to comment.