Skip to content

Support HSQLDB generated keys column name array in SimpleJdbcInsert #34790

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

Open
jsotuyod opened this issue Apr 22, 2025 · 0 comments
Open

Support HSQLDB generated keys column name array in SimpleJdbcInsert #34790

jsotuyod opened this issue Apr 22, 2025 · 0 comments
Labels
status: waiting-for-triage An issue we've not yet triaged or decided on

Comments

@jsotuyod
Copy link

jsotuyod commented Apr 22, 2025

The HSQLDB JDBC Driver (starting at 2.0) will return true when queried for getGeneratedKeys support at

if (databaseMetaData.supportsGetGeneratedKeys()) {
logger.debug("GetGeneratedKeys is supported");
setGetGeneratedKeysSupported(true);

However, all versions of HSQLDB are explicitly blocked from specifying column names to retrieve as per

private static final List<String> productsNotSupportingGeneratedKeysColumnNameArray =
Arrays.asList("Apache Derby", "HSQL Database Engine");

and instead fallback to using a generic Statement.RETURN_GENERATED_KEYS flag

private PreparedStatement prepareStatementForGeneratedKeys(Connection con) throws SQLException {
if (getGeneratedKeyNames().length < 1) {
throw new InvalidDataAccessApiUsageException("Generated Key Name(s) not specified. " +
"Using the generated keys features requires specifying the name(s) of the generated column(s).");
}
PreparedStatement ps;
if (this.tableMetaDataContext.isGeneratedKeysColumnNameArraySupported()) {
if (logger.isDebugEnabled()) {
logger.debug("Using generated keys support with array of column names.");
}
ps = con.prepareStatement(getInsertString(), getGeneratedKeyNames());
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Using generated keys support with Statement.RETURN_GENERATED_KEYS.");
}
ps = con.prepareStatement(getInsertString(), Statement.RETURN_GENERATED_KEYS);
}

However, at least on current versions of HSQLDB, the support for column names is present, and even documented

getGeneratedKeys

Starting with version 2.0, HSQLDB supports this feature with single-row and multi-row insert, update and merge statements.
This method returns a result set only if the executeUpdate methods that was used is one of the three methods that have the extra parameter indicating return of generated keys

If the executeUdate method did not specify the columns which represent the auto-generated keys the IDENTITY column or GENERATED column(s) of the table are returned.

The executeUpdate methods with column indexes or column names return the post-insert or post-update values of the specified columns, whether the columns are generated or not. This allows values that have been modified by execution of triggers to be returned.

If column names or indexes provided by the user in the executeUpdate() method calls do not correspond to table columns (incorrect names or indexes larger than the column count), an empty result is returned.

source

Why does it matter? Because using Statement.RETURN_GENERATED_KEYS will only return columns that are either an IDENTITY or GENERATED column; but a primary key may be generated through a different means. For instance, take this simple table:

CREATE TABLE DEMO(
  id UUID NOT NULL DEFAULT uuid(),
  val VARCHAR(10) NOT NULL,
  PRIMARY KEY(id)
);

Given the following SimpleJdbcInsert:

SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(ds)
  .withTableName("DEMO")
  .usingColumns("val")
  .usingGeneratedKeyColumns("id");

KeyHolder keyHolder = jdbcInsert.executeAndReturnKeyHolder(Map.of("val", "foo"));
// keyHolder.getKeys() will be empty!

However, completely bypassing the check from jdbc insert (pardon the messy code to get it done), will yield the correct result:

SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(ds)
  .withTableName("DEMO")
  .usingColumns("val")
  .usingGeneratedKeyColumns("id");

// manually compile to help bypass
jdbcInsert.compile();

KeyHolder keyHolder = new GeneratedKeyHolder();

// ugly and shameless copy pasting of pieces of AbstractJdbcInsert just to prove a point
jdbcInsert.getJdbcTemplate().update(
  con -> {
    PreparedStatement ps = con.prepareStatement(jdbcInsert.getInsertString(), jdbcInsert.getGeneratedKeyNames());
    int[] insertTypes = jdbcInsert.getInsertTypes();
    StatementCreatorUtils.setParameterValue(ps, 1, insertTypes[0], "foo");
    return ps;
  },
  keyHolder);

// keyHolder.getKeys() will contain the uuid!

Other database drivers, such as PostgreSQL, will work as expected when given an equivalent table (ie: setting a uuid primary key through a DEFAULT gen_random_uuid())

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged or decided on
Projects
None yet
Development

No branches or pull requests

2 participants