| /*=============================================================================# |
| # 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"); |
| } |
| |
| } |