/* * Copyright 2017-present Facebook, Inc. * * 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.facebook.buck.rules; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.Optional; import org.sqlite.BusyHandler; public class SQLiteBuildInfoStore implements BuildInfoStore { private final Connection connection; private final PreparedStatement selectStmt; private final PreparedStatement updateStmt; private final PreparedStatement deleteStmt; public SQLiteBuildInfoStore(ProjectFilesystem filesystem) throws IOException { String dbPath = filesystem .getRootPath() .resolve(filesystem.getBuckPaths().getScratchDir().resolve("metadata.db")) .toString(); try { Class.forName("org.sqlite.JDBC"); connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); connection.createStatement().executeUpdate("PRAGMA SYNCHRONOUS = OFF"); connection.createStatement().executeUpdate("PRAGMA JOURNAL_MODE = WAL"); connection .createStatement() .executeUpdate( "CREATE TABLE IF NOT EXISTS metadata " + "(target TEXT, key TEXT, value TEXT, " + "PRIMARY KEY (target, key)) " + "WITHOUT ROWID"); selectStmt = connection.prepareStatement("SELECT value FROM metadata WHERE target = ? AND key = ?"); updateStmt = connection.prepareStatement( "INSERT OR REPLACE INTO metadata (target, key, value) VALUES (?, ?, ?)"); deleteStmt = connection.prepareStatement("DELETE FROM metadata WHERE target = ?"); BusyHandler busyHandler = new BusyHandler() { @Override protected int callback(int retries) throws SQLException { try { Thread.sleep(retries); } catch (InterruptedException e) { throw new SQLException(e); } return 1; } }; BusyHandler.setHandler(connection, busyHandler); } catch (ClassNotFoundException | SQLException e) { throw new IOException(e); } } @Override public void close() { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public synchronized Optional<String> readMetadata(BuildTarget buildTarget, String key) { try { selectStmt.setString(1, buildTarget.getFullyQualifiedName()); selectStmt.setString(2, key); ResultSet rs = selectStmt.executeQuery(); if (!rs.next()) { return Optional.empty(); } String value = rs.getString(1); return Optional.of(value); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public synchronized void updateMetadata(BuildTarget buildTarget, Map<String, String> metadata) throws IOException { try { for (Map.Entry<String, String> e : metadata.entrySet()) { updateStmt.setString(1, buildTarget.getFullyQualifiedName()); updateStmt.setString(2, e.getKey()); updateStmt.setString(3, e.getValue()); updateStmt.addBatch(); } updateStmt.executeBatch(); } catch (SQLException e) { throw new IOException(e); } } @Override public synchronized void deleteMetadata(BuildTarget buildTarget) throws IOException { try { deleteStmt.setString(1, buildTarget.getFullyQualifiedName()); deleteStmt.executeUpdate(); } catch (SQLException e) { throw new IOException(e); } } }