Subject Re: Updateable ResultSets - any progress?
Author Roman Rokytskyy
> I have a database in front of me with 1 or 2 large tables, and
> typically a row from one of these is to be displayed (say 50 fields),
> then the user can edit *any* field. An updateable ResultSet would be
> nice, saving me from having to write lots of code/SQL to decide what
> (if anything) the user has updated. Especially as the database
> structure may change many times over the next few months (but the
> column names are unlikely to change).

As I probably already described, main problem in implementing this
feature is need to rewrite the SQL submitted by user to add necessary
RDB$DB_KEY field(s). Having done this you can always submit an update

UPDATE tableName
SET someField = ?
WHERE RDB$DB_KEY = ?

Some additional care should be taken when read-committed tx isolation
level is used, but that's it.

Until then you have following options:

a) if you do not need scrolling (only forward navigation), you can
always use "UPDATE ... WHERE CURRENT OF <cursor name>".

b) if you have some time, you can implement these methods that will
only work if user submits an SQL containing RDB$DB_KEY. It should not
be a complex task.

c) code updates into your application. You can create pretty generic
solution by introducing an "update object" class that contains name of
the table, value of the RDB$DB_KEY or, even better, your primary key.
Then in your application, when field is changed, you have to do a
pretty simple thing:

public void fieldChanged(Object pk, int columnIndex, ResultSet rs) {
UpdateObject obj = (UpdateObject)updateCache.get(pk);
if (obj == null) {
obj = new UpdateObject();
// we assume that only one table is in ResultSet
obj.setTableName(rs.getMetaData().getTableName(columnIndex);
int maxCols = rs.getMetaData().getColumnCount();
for (int i = 1 ; i <= maxCols; i++) {
obj.setColumnName(i, rs.getMetaData().getColumnName(i));
}
updateCache.put(pk, obj);
}

obj.setColumnValue(columnIndex, rs.getObject(columnIndex));
}

At the end (when user presses "Save changes" button) you have to
construct update like this:

Iterator iter = updateCache.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
Object pk = entry.getKey();
UpdateObject obj = (UpdateObject)entry.getValue();

StringBuffer sb = new StringBuffer();
sb.append("UPDATE ").append(obj.getTableName()).append(" SET ");
boolean firstColumn = true;
for (int i = 1; i <= obj.getColumnCount(); i++) {
if (obj.isChanged(i)) {
// simple flag to separate columns with commas
if (!firstColumn) {
sb.append(", ");
} else
firstColumn = false;

sb.append(obj.getColumnName(i)).append(" = ? ");
}
}
sb.append("WHERE ").append(pkColumnName).append(" = ?");

PreparedStatement stmt =
connection.prepareStatement(sb.toString());

for (int i = 1; i <= obj.getColumnCount(); i++) {
stmt.setObject(i, obj.getColumnValue(i));
}

stmt.execute();
}

This code is pretty generic and will work even if you change names of
your columns. It requires:

a) your result set has only one table (if more than one table is used
you have to add some logic to deal with PKs)

b) your primary key consists of single column

c) you know the name of your primary key column

The rest is pretty generic and can be reused in different applications.

> Anyway, has there been any interest in implementing updateable
> ResultSets?

On Fulda conference I had a discussion with Nickolay Samofatov, core
engine developer. We agreed that there will be new Firebird API call
that will return RDB$DB_KEY field(s) without SQL rewriting. When this
feature is available, we will implement updatable result sets.

> Or is there another Firebird JDBC driver that does support them?

As far as I know, the only "other" driver is InterClient 1.6 or 2.0.1,
but they do not support updatable cursors.

Roman