Skip to content
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
2 changes: 1 addition & 1 deletion src/sql/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,5 @@ For postgres, setup the fantest database and user account via:
In addition, both JDBC drivers must be installed, and "etc/sql/config.props"
must have a reference to the classpath of both drivers, e.g.

java.drivers=java.drivers=com.mysql.cj.jdbc.Driver,org.postgresql.Driver
java.drivers=com.mysql.cj.jdbc.Driver,org.postgresql.Driver

18 changes: 11 additions & 7 deletions src/sql/fan/BatchExecutor.fan
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,25 @@ class BatchExecutor
queued.add(params)
if (queued.size > maxChunkSize)
{
results.addAll(stmt.executeBatch(queued))
r := stmt.executeBatch(queued)
result.updateCounts.addAll(r.updateCounts)
result.keys.addAll(r.keys)
queued.clear
}
}

** Finish execution, and return the concatenated results from each
** call to Statement.executeBatch().
Int?[] finish()
** Finish execution, and return the collated results from each call
** to Statement.executeBatch().
BatchResult finish()
{
if (queued.size > 0)
{
results.addAll(stmt.executeBatch(queued))
r := stmt.executeBatch(queued)
result.updateCounts.addAll(r.updateCounts)
result.keys.addAll(r.keys)
queued.clear
}
return results
return result
}

//////////////////////////////////////////////////////////////////////////
Expand All @@ -53,6 +57,6 @@ class BatchExecutor
private Int maxChunkSize

private [Str:Obj][] queued := [Str:Obj][,]
private Int?[] results := [,]
private BatchResult result := BatchResult(Int?[,], Obj?[,])
}

43 changes: 36 additions & 7 deletions src/sql/fan/Statement.fan
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,31 @@ class Statement

**
** Execute a batch of commands on a prepared Statement. If all commands
** execute successfully, returns an array of update counts.
** execute successfully, return a BatchResult containing an array of update
** counts, and an array of auto-generated keys.
**
** For each element in the array, if the element is non-null, then it
** represents an update count giving the number of rows in the database that
** were affected by the command's execution.
** For each element in the update counts array, if the element is non-null,
** then it represents an update count giving the number of rows in the
** database that were affected by the command's execution. If a given array
** element is null, it indicates that the command was processed successfully
** but that the number of rows affected is unknown.
**
** If a given array element is null, it indicates that the command was
** processed successfully but that the number of rows affected is unknown.
** The keys array contains the auto-generated keys, if any, for each command.
** An element is null if the driver did not return a key for that command.
** Note that some drivers return the keys even if they were not
** auto-generated.
**
** If the driver returns auto-generated keys for some but not all of the
** commands, throw SqlErr since the keys cannot be correlated to their
** commands.
**
** If one of the commands in a batch update fails to execute properly, this
** method throws a SqlErr that wraps a java.sql.BatchUpdateException, and a
** JDBC driver may or may not continue to process the remaining commands in
** the batch, consistent with the underlying DBMS -- either always continuing
** to process commands or never continuing to process commands.
**
native Int?[] executeBatch([Str:Obj]?[] paramsList)
native BatchResult executeBatch([Str:Obj]?[] paramsList)

**
** If the last execute has more results from a multi-result stored
Expand Down Expand Up @@ -118,3 +127,23 @@ class Statement

}

**************************************************************************
** BatchResult
**************************************************************************

** BatchResult contains the result of a call to Statement.executeBatch().
class BatchResult
{
new make(Int?[] updateCounts, Obj?[] keys)
{
this.updateCounts = updateCounts
this.keys = keys
}

** The number of rows that were affected by each command's execution.
Int?[] updateCounts

** The keys that were auto-generated by each command.
Obj?[] keys
}

65 changes: 57 additions & 8 deletions src/sql/java/StatementPeer.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,7 @@ private Object executeResult(Statement self, boolean isResultSet)
List keys = null;
while (rs.next())
{
// get key as Long or String
Object key;
try { key = rs.getLong(1); }
catch (Exception e) { key = rs.getString(1); }
Object key = readGenKey(rs);

// lazily create keys list with proper type
if (keys == null)
Expand All @@ -330,12 +327,60 @@ private Object executeResult(Statement self, boolean isResultSet)
return Long.valueOf(-1);
}

public List executeBatch(Statement self, List paramsList)
/**
* Read the auto-generated key for the current row as a Long, or as a
* String for databases like Oracle which do not allow access as an Int.
*/
private static Object readGenKey(ResultSet rs)
throws SQLException
{
try { return rs.getLong(1); }
catch (Exception e) { return rs.getString(1); }
}

/**
* Auto-generated keys for the batch just executed, with one entry per
* command. Entries are null if the driver returned no keys at all. If
* the driver returns keys for some but not all of the commands they cannot
* be correlated to their commands, so that case is an error.
*/
private List readBatchGenKeys(PreparedStatement pstmt, int numCommands)
throws SQLException
{
List keys = List.make(Sys.ObjType.toNullable(), numCommands);

if (isAutoKeys)
{
ResultSet rs = pstmt.getGeneratedKeys();
try
{
while (rs.next()) keys.add(readGenKey(rs));
}
finally
{
rs.close();
}

if (keys.size() == numCommands) return keys;

if (keys.size() != 0)
throw SqlErr.make("Driver returned " + keys.size() +
" auto-generated keys for " + numCommands + " batch commands");
}

// no keys returned: one null per command
for (int i = 0; i < numCommands; i++) keys.add(null);
return keys;
}

public BatchResult executeBatch(Statement self, List paramsList)
{
if (!prepared)
throw SqlErr.make("Statement has not been prepared.");
PreparedStatement pstmt = (PreparedStatement)stmt;

List genKeys = List.make(Sys.ObjType.toNullable(), 0);

try
{
// add batch
Expand All @@ -348,8 +393,11 @@ public List executeBatch(Statement self, List paramsList)
// execute batch
int[] exec = pstmt.executeBatch();

// gather auto-generated keys before the statement is reused
genKeys = readBatchGenKeys(pstmt, (int)paramsList.size());

// process result
List result = List.make(Sys.IntType, exec.length);
List updateCounts = List.make(Sys.IntType, exec.length);
for (int i = 0; i < exec.length; i++)
{
int n = exec[i];
Expand All @@ -358,9 +406,10 @@ public List executeBatch(Statement self, List paramsList)
// java.sql.Statement.SUCCESS_NO_INFO. We treat that as a null,
// indicating that the command was processed successfully but that the
// number of rows affected is unknown.
result.add(n < 0 ? null : (long)n);
updateCounts.add(n < 0 ? null : (long)n);
}
return result;

return BatchResult.make(updateCounts, genKeys);
}
catch (SQLException ex)
{
Expand Down
Loading