opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
@@ -134,6 +134,23 @@ return tree2table.get(treeName); } /** * The form a catalog pattern has to take to match an identifier this backend created unquoted. * An unquoted identifier is folded when it is stored - to upper case on oracle, to lower case * on postgresql - and a metadata pattern is matched against the stored form, not against the * name as it was written. The driver is asked which way it folds, rather than its class name * being matched, since this is what the JDBC contract exposes these two methods for. */ static String storedIdentifier(DatabaseMetaData metaData, String name) throws SQLException { if (metaData.storesUpperCaseIdentifiers()) { return name.toUpperCase(); } if (metaData.storesLowerCaseIdentifiers()) { return name.toLowerCase(); } return name; } @Override public void removeStorageFiles() throws StorageRuntimeException { final boolean isOpen=getStorageStatus().isWorking(); @@ -266,10 +283,21 @@ } boolean isExistsTable(TreeName treeName) { try (final ResultSet rs = con.getMetaData().getTables(null, null, null, new String[]{"TABLE"})) { while (rs.next()) { if (tree2table.get(treeName).equalsIgnoreCase(rs.getString("TABLE_NAME"))) { return true; final String tableName = getTableName(treeName); try { final DatabaseMetaData metaData = con.getMetaData(); // asked of the catalog by name: openTree(createOnDemand) calls this for every tree // of the backend - about 25 of them for a stock suffix, on every open - and listing // every table of the database each time costs the whole catalog once per tree, on a // database this backend may well be sharing with something else try (final ResultSet rs = metaData.getTables(null, null, storedIdentifier(metaData, tableName), new String[]{"TABLE"})) { while (rs.next()) { // the name still has to be compared: "_" is a single-character wildcard in a // metadata pattern, so "opendj_<hash>" also matches a table named "opendjX<hash>" if (tableName.equalsIgnoreCase(rs.getString("TABLE_NAME"))) { return true; } } } } catch (Exception e) { opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/TestCase.java
@@ -139,6 +139,57 @@ } /** * openTree() and deleteTree() ask the catalog whether the table of a tree is there. The name is * looked up in the form the catalog stores it - an unquoted identifier is folded to upper case * on oracle and to lower case on postgresql - so getting that wrong makes a second open try to * create a table that is already there, and a second delete drop one that is already gone (#885). */ @Test public void testTableOfATreeIsFoundByName() throws Exception { final JDBCStorage storage = new JDBCStorage(createBackendCfg(), null); final TreeName tree = new TreeName("testCatalogLookup", "tree"); try { storage.open(AccessMode.READ_WRITE); storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { txn.openTree(tree, true); txn.put(tree, key(1), value(1)); } }); // the table is there now: opening the tree again must find it, not create it a second time storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { txn.openTree(tree, true); } }); assertEquals(storage.read(new ReadOperation<ByteString>() { @Override public ByteString run(ReadableTransaction txn) throws Exception { return txn.read(tree, key(1)); } }), value(1)); storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { txn.deleteTree(tree); } }); // and gone now: deleting it again must find nothing rather than drop what is not there storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { txn.deleteTree(tree); } }); } finally { storage.close(); } } /** * Forward repositioning inside the already-fetched batch must be served from the buffer without SQL, * and batch sizes must grow from "fetchsize.initial" to "fetchsize" on sequential reads (#860). */