All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.schema.H2AccountPatchReviewStore Maven / Gradle / Ivy

// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.schema;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.change.AccountPatchReviewStore;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Singleton
public class H2AccountPatchReviewStore
    implements AccountPatchReviewStore, LifecycleListener {
  private static final Logger log =
      LoggerFactory.getLogger(H2AccountPatchReviewStore.class);

  public static class Module extends LifecycleModule {
    @Override
    protected void configure() {
      DynamicItem.bind(binder(), AccountPatchReviewStore.class)
          .to(H2AccountPatchReviewStore.class);
      listener().to(H2AccountPatchReviewStore.class);
    }
  }

  @VisibleForTesting
  public static class InMemoryModule extends LifecycleModule {
    @Override
    protected void configure() {
      H2AccountPatchReviewStore inMemoryStore = new H2AccountPatchReviewStore();
      DynamicItem.bind(binder(), AccountPatchReviewStore.class)
          .toInstance(inMemoryStore);
      listener().toInstance(inMemoryStore);
    }
  }

  private final String url;

  @Inject
  H2AccountPatchReviewStore(@GerritServerConfig Config cfg,
      SitePaths sitePaths) {
    this.url = H2.appendUrlOptions(cfg, getUrl(sitePaths));
  }

  public static String getUrl(SitePaths sitePaths) {
    return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
  }

  /**
   * Creates an in-memory H2 database to store the reviewed flags.
   * This should be used for tests only.
   */
  @VisibleForTesting
  private H2AccountPatchReviewStore() {
    // DB_CLOSE_DELAY=-1: By default the content of an in-memory H2 database is
    // lost at the moment the last connection is closed. This option keeps the
    // content as long as the vm lives.
    this.url = "jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1";
  }

  @Override
  public void start() {
    try {
      createTableIfNotExists(url);
    } catch (OrmException e) {
      log.error("Failed to create table to store account patch reviews", e);
    }
  }

  public static void createTableIfNotExists(String url) throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        Statement stmt = con.createStatement()) {
      stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ACCOUNT_PATCH_REVIEWS ("
          + "ACCOUNT_ID INTEGER DEFAULT 0 NOT NULL, "
          + "CHANGE_ID INTEGER DEFAULT 0 NOT NULL, "
          + "PATCH_SET_ID INTEGER DEFAULT 0 NOT NULL, "
          + "FILE_NAME VARCHAR(255) DEFAULT '' NOT NULL, "
          + "CONSTRAINT PRIMARY_KEY_ACCOUNT_PATCH_REVIEWS "
          + "PRIMARY KEY (ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME)"
          + ")");
    } catch (SQLException e) {
      throw convertError("create", e);
    }
  }

  public static void dropTableIfExists(String url) throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        Statement stmt = con.createStatement()) {
      stmt.executeUpdate("DROP TABLE IF EXISTS ACCOUNT_PATCH_REVIEWS");
    } catch (SQLException e) {
      throw convertError("create", e);
    }
  }

  @Override
  public void stop() {
  }

  @Override
  public boolean markReviewed(PatchSet.Id psId, Account.Id accountId,
      String path) throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        PreparedStatement stmt =
            con.prepareStatement("INSERT INTO ACCOUNT_PATCH_REVIEWS "
                + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES "
                + "(?, ?, ?, ?)")) {
      stmt.setInt(1, accountId.get());
      stmt.setInt(2, psId.getParentKey().get());
      stmt.setInt(3, psId.get());
      stmt.setString(4, path);
      stmt.executeUpdate();
      return true;
    } catch (SQLException e) {
      OrmException ormException = convertError("insert", e);
      if (ormException instanceof OrmDuplicateKeyException) {
        return false;
      }
      throw ormException;
    }
  }

  @Override
  public void markReviewed(PatchSet.Id psId, Account.Id accountId,
      Collection paths) throws OrmException {
    if (paths == null || paths.isEmpty()) {
      return;
    }

    try (Connection con = DriverManager.getConnection(url);
        PreparedStatement stmt =
            con.prepareStatement("INSERT INTO ACCOUNT_PATCH_REVIEWS "
                + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES "
                + "(?, ?, ?, ?)")) {
      for (String path : paths) {
        stmt.setInt(1, accountId.get());
        stmt.setInt(2, psId.getParentKey().get());
        stmt.setInt(3, psId.get());
        stmt.setString(4, path);
        stmt.addBatch();
      }
      stmt.executeBatch();
    } catch (SQLException e) {
      OrmException ormException = convertError("insert", e);
      if (ormException instanceof OrmDuplicateKeyException) {
        return;
      }
      throw ormException;
    }
  }

  @Override
  public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path)
      throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        PreparedStatement stmt =
            con.prepareStatement("DELETE FROM ACCOUNT_PATCH_REVIEWS "
                + "WHERE ACCOUNT_ID = ? AND CHANGE_ID + ? AND "
                + "PATCH_SET_ID = ? AND FILE_NAME = ?")) {
      stmt.setInt(1, accountId.get());
      stmt.setInt(2, psId.getParentKey().get());
      stmt.setInt(3, psId.get());
      stmt.setString(4, path);
      stmt.executeUpdate();
    } catch (SQLException e) {
      throw convertError("delete", e);
    }
  }

  @Override
  public void clearReviewed(PatchSet.Id psId) throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        PreparedStatement stmt =
            con.prepareStatement("DELETE FROM ACCOUNT_PATCH_REVIEWS "
                + "WHERE CHANGE_ID + ? AND PATCH_SET_ID = ?")) {
      stmt.setInt(1, psId.getParentKey().get());
      stmt.setInt(2, psId.get());
      stmt.executeUpdate();
    } catch (SQLException e) {
      throw convertError("delete", e);
    }
  }

  @Override
  public Collection findReviewed(PatchSet.Id psId, Account.Id accountId)
      throws OrmException {
    try (Connection con = DriverManager.getConnection(url);
        PreparedStatement stmt =
            con.prepareStatement("SELECT FILE_NAME FROM ACCOUNT_PATCH_REVIEWS "
                + "WHERE ACCOUNT_ID = ? AND CHANGE_ID = ? AND PATCH_SET_ID = ?")) {
      stmt.setInt(1, accountId.get());
      stmt.setInt(2, psId.getParentKey().get());
      stmt.setInt(3, psId.get());
      try (ResultSet rs = stmt.executeQuery()) {
        List files = new ArrayList<>();
        while (rs.next()) {
          files.add(rs.getString("FILE_NAME"));
        }
        return files;
      }
    } catch (SQLException e) {
      throw convertError("select", e);
    }
  }

  public static OrmException convertError(String op, SQLException err) {
    switch (getSQLStateInt(err)) {
      case 23001: // UNIQUE CONSTRAINT VIOLATION
      case 23505: // DUPLICATE_KEY_1
        return new OrmDuplicateKeyException("ACCOUNT_PATCH_REVIEWS", err);

      default:
        if (err.getCause() == null && err.getNextException() != null) {
          err.initCause(err.getNextException());
        }
        return new OrmException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
    }
  }

  private static String getSQLState(SQLException err) {
    String ec;
    SQLException next = err;
    do {
      ec = next.getSQLState();
      next = next.getNextException();
    } while (ec == null && next != null);
    return ec;
  }

  private static int getSQLStateInt(SQLException err) {
    String s = getSQLState(err);
    if (s != null) {
      Integer i = Ints.tryParse(s);
      return i != null ? i : -1;
    }
    return 0;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy