mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
2 days ago ea06fd66806102fb9790b336afc9770091de3767
opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/MySqlTestCase.java
@@ -15,12 +15,20 @@
 */
package org.opends.server.backends.jdbc;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.backends.pluggable.spi.AccessMode;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@@ -174,6 +182,121 @@
        throw new AssertionError("an account granted one query an hour must be refused within four connects");
    }
    /** The database of the case below: a directory next to the one of this suite, on the same server. */
    private static final String NEIGHBOUR = "opendj_neighbour1075";
    @Override
    protected void dropStaleNeighbours() throws SQLException {
        grant(getJdbcUrl(), "drop database if exists " + NEIGHBOUR);
    }
    @DataProvider
    public Object[][] databaseTerms() {
        return new Object[][] {
            // the default of Connector/J: the database is the catalog, and the lookups are asked in it
            { "catalog", "" },
            // the database is the schema: the connection names no catalog, the lookups span the server,
            // and only the schema path read off the connection tells the neighbour apart
            { "schema", "?databaseTerm=SCHEMA" },
        };
    }
    /**
     * A table and an index of another database of this server answer for none of this backend's, however
     * the connection names its database (#1075) - the mysql twin of the postgres case of #902.
     * <p>
     * A table is named after its tree and an index after its table, so two directories on one server - the
     * stock backend id in two databases - hold the same table and the same index. Connector/J asked with no
     * catalog lists the tables of every database, and both guards of {@code openTree()} would then skip a
     * create this backend needs: the table one leaves every statement addressing a table that is not there,
     * and the index one - the quiet half - leaves the {@code where k>? order by k} batches of every cursor
     * a full scan for the life of the deployment.
     * <p>
     * Under the default {@code databaseTerm} the guards hold by two layers at once - the catalog they pass
     * and the database {@code TableScope.covers()} reads off every row - and this case goes red only when
     * both give way; the unit cases of {@code JDBCStorageRetryTest} pin each of them. Under
     * {@code databaseTerm=SCHEMA} the connection names no catalog and the schema path is the one layer
     * there is, which is what this case exists for: what it rests on is how the driver lists a table of
     * another database, and only a live server answers that.
     */
    @Test(dataProvider = "databaseTerms")
    public void testAnOpenIsAnsweredForByNoTableOfAnotherDatabase(String term, String urlOptions) throws Exception {
        final TreeName tree = new TreeName("testAnotherDatabase", "tree");
        final JDBCStorage storage =
            new JDBCStorage(createBackendCfg(getBackendId() + "_" + term, getJdbcUrl() + urlOptions), null);
        final String tableName = storage.getTableName(tree);
        final String indexName = "k_" + tableName.substring("opendj_".length());
        try {
            // the neighbouring directory: the same table and the same index, in a database this storage
            // reaches through no unqualified name of its own. Spelled out rather than opened by a storage,
            // so that the fixture is the collision and nothing else
            grant(getJdbcUrl(), "drop database if exists " + NEIGHBOUR,
                "create database " + NEIGHBOUR,
                "create table " + NEIGHBOUR + "." + tableName
                    + " (h char(128),k varbinary(255),v longblob,primary key(h,k))",
                "create index " + indexName + " on " + NEIGHBOUR + "." + tableName + " (k)");
            assertFalse(isExistsIn(DATABASE, tableName, null),
                "the case did not start with the table of this backend absent from its database");
            storage.open(AccessMode.READ_WRITE);
            storage.write(new WriteOperation() {
                @Override
                public void run(WriteableTransaction txn) throws Exception {
                    txn.openTree(tree, true);
                    // the destructive half of the table guard, and the reason it is loud: found abroad, the
                    // table is created nowhere and this statement addresses a table that is not there
                    txn.put(tree, ByteString.valueOfUtf8("a key of this backend"),
                        ByteString.valueOfUtf8("a value of this backend"));
                }
            });
            assertTrue(isExistsIn(DATABASE, tableName, null),
                "the open took the table of another database for its own and created none");
            assertTrue(isExistsIn(DATABASE, tableName, indexName),
                "the open took the index of another database for its own: the cursor batches of this tree are full scans behind it");
            assertEquals(rowCountIn(DATABASE, tableName), 1,
                "the write of this backend landed in a table other than the one the open made");
            assertEquals(rowCountIn(NEIGHBOUR, tableName), 0,
                "the write of this backend landed in the table of the neighbouring database");
        } finally {
            clearQuietly(storage);
            grant(getJdbcUrl(), "drop database if exists " + NEIGHBOUR);
        }
    }
    /** The database of the connections of this suite. */
    private static final String DATABASE = "database_name";
    /**
     * Whether that one database holds the table - or, given an index name, that index of it - asked of
     * information_schema by name rather than through the catalog lookups under test.
     */
    private boolean isExistsIn(String database, String tableName, String indexName) throws SQLException {
        final String query = indexName == null
            ? "select 1 from information_schema.tables where table_schema=? and table_name=?"
            : "select 1 from information_schema.statistics where table_schema=? and table_name=? and index_name=?";
        try (final Connection con = DriverManager.getConnection(getJdbcUrl());
             final PreparedStatement st = con.prepareStatement(query)) {
            st.setString(1, database);
            st.setString(2, tableName);
            if (indexName != null) {
                st.setString(3, indexName);
            }
            try (final ResultSet rs = st.executeQuery()) {
                return rs.next();
            }
        }
    }
    /** What the table of that one database holds, which says which of the two tables a write went to. */
    private int rowCountIn(String database, String tableName) throws SQLException {
        try (final Connection con = DriverManager.getConnection(getJdbcUrl());
             final Statement st = con.createStatement();
             final ResultSet rs = st.executeQuery("select count(*) from " + database + "." + tableName)) {
            return rs.next() ? rs.getInt(1) : -1;
        }
    }
    /** The account of the test is made and unmade on the connection of the suite's own credentials. */
    private static void grant(String url, String... statements) throws SQLException {
        try (final Connection admin = DriverManager.getConnection(url);