blob: c71e359239d1faf2add7d2dc7f043149561b3f6a [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.core.pkgmanager;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.eclipse.statet.ecommons.edb.core.EmbeddedDB;
import org.eclipse.statet.internal.r.core.RCorePlugin;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.pkgmanager.IRPkgInfo;
import org.eclipse.statet.r.core.pkgmanager.RPkgInfo;
import org.eclipse.statet.rj.renv.core.BasicRPkgCompilation;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.renv.core.RLibGroup;
import org.eclipse.statet.rj.renv.core.RLibLocation;
import org.eclipse.statet.rj.renv.core.RNumVersion;
final class DB {
/** DB definitions */
@SuppressWarnings({ "hiding", "nls" })
private static final class REnvDB { // SCHMEMA
static final String NAME= "RENV";
static final class LibPaths {
static final String NAME= "LIBPATHS";
static final String QNAME= REnvDB.NAME + '.' + NAME;
static final String COL_ID= "LIB_ID";
static final String COL_LIB_PATH= "LIB_PATH";
static final String COL_STAMP= "STAMP";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_ID + " int not null "
+ "primary key "
+ "generated always as identity, "
+ COL_LIB_PATH + " varchar(4096) not null unique, "
+ COL_STAMP + " bigint"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_LIB_PATH + ") "
+ "values (?)";
static final String OP_delete_byPath= "delete from " + QNAME + " "
+ "where (" + COL_LIB_PATH + "= ?)";
static final String OP_getAll= "select "
+ COL_ID + ", "
+ COL_LIB_PATH + " "
+ "from " + QNAME;
}
static final class Pkgs {
static final String NAME= "PKGS";
static final String QNAME= REnvDB.NAME + '.' + NAME;
static final String COL_LIB_ID= "LIB_ID";
static final String COL_NAME= "NAME";
static final String COL_VERSION= "VERSION";
static final String COL_BUILT= "BUILT";
static final String COL_TITLE= "TITLE";
static final String COL_FLAGS= "FLAGS";
static final String COL_INST_STAMP= "INST_STAMP";
static final String COL_REPO_ID= "REPO_ID";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_LIB_ID + " int not null "
+ "references " + LibPaths.QNAME + " on delete cascade, "
+ COL_NAME + " varchar(64) not null, "
+ COL_VERSION + " varchar(64) not null, "
+ COL_BUILT + " varchar(256) not null, "
+ COL_TITLE + " varchar(256) not null, "
+ COL_FLAGS + " int, "
+ COL_INST_STAMP + " bigint, "
+ COL_REPO_ID + " varchar(256), "
+ "primary key ("
+ COL_LIB_ID + ", "
+ COL_NAME + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_LIB_ID + ", "
+ COL_NAME + ", "
+ COL_VERSION + ", "
+ COL_BUILT + ", "
+ COL_TITLE + ", "
+ COL_FLAGS + ", "
+ COL_INST_STAMP + ", "
+ COL_REPO_ID + ") "
+ "values (? , ?, ?, ?, ?, ?, ?, ?)";
static final String OP_update= "update " + QNAME + " set "
+ COL_VERSION + "= ?, "
+ COL_BUILT + "= ?, "
+ COL_TITLE + "= ?, "
+ COL_FLAGS + "= ?, "
+ COL_INST_STAMP + "= ?, "
+ COL_REPO_ID +"= ? "
+ "where (" + COL_LIB_ID + "= ? and " + COL_NAME + "= ?)";
static final String OP_delete= "delete from " + QNAME + " "
+ "where (" + COL_LIB_ID + "= ? and " + COL_NAME + "= ?)";
static final String OP_get_ofLib= "select "
+ COL_NAME + ", "
+ COL_VERSION + ", "
+ COL_BUILT + ", "
+ COL_TITLE + ", "
+ COL_FLAGS + ", "
+ COL_INST_STAMP + ", "
+ COL_REPO_ID + " "
+ "from " + QNAME + " "
+ "where (" + COL_LIB_ID + "= ?)";
}
}
private static RLibLocation getLibLocation(final List<? extends RLibGroup> envLibs, final String path) {
for (final RLibGroup group : envLibs) {
for (final RLibLocation location : group.getLibLocations()) {
if (path.equals(location.getDirectory())) {
return location;
}
}
}
return null;
}
static DB create(final REnv rEnv, final IFileStore parent) {
try {
final File file= parent.getChild("db").toLocalFile(EFS.NONE, null); //$NON-NLS-1$
final ConnectionFactory connectionFactory= EmbeddedDB.createConnectionFactory(file.getAbsolutePath());
return new DB(rEnv, connectionFactory);
}
catch (final CoreException e) {
return null;
}
}
private final REnv rEnv;
private final Map<String, Integer> libIdMap= new HashMap<>();
private final ConnectionFactory connectionFactory;
private Connection connection;
private PreparedStatement libAddStatement;
private PreparedStatement pkgDeleteStatement;
private PreparedStatement pkgAddStatement;
private PreparedStatement pkgChangeStatement;
private DB(final REnv rEnv, final ConnectionFactory connectionFactory) throws CoreException {
this.rEnv= rEnv;
this.connectionFactory= connectionFactory;
}
private Connection getConnection() throws SQLException {
if (this.connection != null) {
try {
if (!this.connection.isClosed()) {
return this.connection;
}
}
catch (final SQLException e) {}
closeOnError();
}
this.connection= this.connectionFactory.createConnection();
this.connection.setAutoCommit(false);
return this.connection;
}
private void closeOnError() {
if (this.connection != null) {
try {
this.connection.close();
}
catch (final SQLException e) {
}
this.connection= null;
this.libAddStatement= null;
this.pkgDeleteStatement= null;
this.pkgAddStatement= null;
this.pkgChangeStatement= null;
}
}
BasicRPkgCompilation<IRPkgInfo> loadInstalled(final List<? extends RLibGroup> envLibs) {
try {
checkDB();
final BasicRPkgCompilation<IRPkgInfo> newInstalled= new BasicRPkgCompilation<>(8);
List<String> removeLibPath= null;
final Connection connection= getConnection();
try (
final Statement libStatement= connection.createStatement();
final PreparedStatement pkgStatement= connection.prepareStatement(
REnvDB.Pkgs.OP_get_ofLib )
) {
final ResultSet libResult= libStatement.executeQuery(REnvDB.LibPaths.OP_getAll);
while (libResult.next()) {
final String libPath= libResult.getString(2);
final RLibLocation location= getLibLocation(envLibs, libPath);
if (location == null) {
if (removeLibPath == null) {
removeLibPath= new ArrayList<>();
}
removeLibPath.add(libPath);
}
else {
final int id= libResult.getInt(1);
this.libIdMap.put(location.getDirectory(), id);
final RPkgListImpl<IRPkgInfo> list= new RPkgListImpl<>(16);
newInstalled.add(location.getDirectory(), list);
pkgStatement.setInt(1, id);
final ResultSet pkgResult= pkgStatement.executeQuery();
while (pkgResult.next()) {
final RPkgInfo pkg= new RPkgInfo(pkgResult.getString(1),
RNumVersion.create(pkgResult.getString(2)),
pkgResult.getString(3), pkgResult.getString(4), location,
pkgResult.getInt(5), pkgResult.getLong(6), pkgResult.getString(7) );
list.add(pkg);
}
}
}
}
if (removeLibPath != null) {
clean(removeLibPath);
}
return newInstalled;
}
catch (final SQLException e) {
closeOnError();
final String name= this.rEnv.getName();
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID,
NLS.bind("An error occurred when loading R package information of " +
"the R environment ''{0}''.", name ), e ));
return null;
}
}
private void checkDB() throws SQLException {
final Connection connection= getConnection();
final ResultSet schemas= connection.getMetaData().getSchemas(null, REnvDB.NAME);
while (schemas.next()) {
if (REnvDB.NAME.equals(schemas.getString(1))) {
return;
}
}
try (final Statement statement= connection.createStatement()) {
statement.execute(REnvDB.LibPaths.DEFINE_1);
statement.execute(REnvDB.Pkgs.DEFINE_1);
connection.commit();
}
catch (final SQLException e) {
closeOnError();
throw e;
}
}
private void clean(final List<String> removeLibPath) throws SQLException {
final Connection connection= getConnection();
try (final PreparedStatement statement= connection.prepareStatement(
REnvDB.LibPaths.OP_delete_byPath )) {
for (final String libPath : removeLibPath) {
statement.setString(1, libPath);
statement.execute();
}
connection.commit();
}
catch (final SQLException e) {
closeOnError();
throw e;
}
}
void updatePkgs(final Change change) {
try {
final Connection connection= getConnection();
final RPkgChangeSet changeSet= change.installedPkgs;
if (!changeSet.deleted.isEmpty()) {
if (this.pkgDeleteStatement == null) {
this.pkgDeleteStatement= connection.prepareStatement(REnvDB.Pkgs.OP_delete);
}
for (final IRPkgInfo pkg : changeSet.deleted) {
final Integer id= this.libIdMap.get(pkg.getLibLocation().getDirectory());
this.pkgDeleteStatement.setInt(1, id.intValue());
this.pkgDeleteStatement.setString(2, pkg.getName());
this.pkgDeleteStatement.execute();
}
}
if (!changeSet.added.isEmpty()) {
if (this.pkgAddStatement == null) {
this.pkgAddStatement= connection.prepareStatement(REnvDB.Pkgs.OP_insert);
}
for (final IRPkgInfo pkg : changeSet.added) {
Integer id= this.libIdMap.get(pkg.getLibLocation().getDirectory());
if (id == null) {
id= addLib(connection, pkg.getLibLocation());
}
this.pkgAddStatement.setInt(1, id.intValue());
this.pkgAddStatement.setString(2, pkg.getName());
this.pkgAddStatement.setString(3, pkg.getVersion().toString());
this.pkgAddStatement.setString(4, pkg.getBuilt());
this.pkgAddStatement.setString(5, pkg.getTitle());
this.pkgAddStatement.setInt(6, pkg.getFlags());
this.pkgAddStatement.setLong(7, pkg.getInstallStamp());
this.pkgAddStatement.setString(8, pkg.getRepoId());
this.pkgAddStatement.execute();
}
}
if (!changeSet.changed.isEmpty()) {
if (this.pkgChangeStatement == null) {
this.pkgChangeStatement= connection.prepareStatement(REnvDB.Pkgs.OP_update);
}
for (final IRPkgInfo pkg : changeSet.changed) {
final Integer id= this.libIdMap.get(pkg.getLibLocation().getDirectory());
this.pkgChangeStatement.setInt(7, id.intValue());
this.pkgChangeStatement.setString(8, pkg.getName());
this.pkgChangeStatement.setString(1, pkg.getVersion().toString());
this.pkgChangeStatement.setString(2, pkg.getBuilt());
this.pkgChangeStatement.setString(3, pkg.getTitle());
this.pkgChangeStatement.setInt(4, pkg.getFlags());
this.pkgChangeStatement.setLong(5, pkg.getInstallStamp());
this.pkgChangeStatement.setString(6, pkg.getRepoId());
this.pkgChangeStatement.execute();
}
}
connection.commit();
}
catch (final SQLException e) {
closeOnError();
final String name= this.rEnv.getName();
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID,
NLS.bind("An error occurred when saving R package information of " +
"the R environment ''{0}''.", name ), e ));
}
}
private Integer addLib(final Connection connection, final RLibLocation location) throws SQLException {
if (this.libAddStatement == null) {
this.libAddStatement= connection.prepareStatement(REnvDB.LibPaths.OP_insert,
new String[] { REnvDB.LibPaths.COL_ID } );
}
this.libAddStatement.setString(1, location.getDirectory());
this.libAddStatement.execute();
final ResultSet result= this.libAddStatement.getGeneratedKeys();
if (result.next()) {
final Integer id= result.getInt(1);
this.libIdMap.put(location.getDirectory(), id);
return id;
}
throw new SQLException("Unexpected result");
}
}