Skip to content

Commit

Permalink
Merge pull request #221 from AdaptiveScale/feature/add_drivers_command
Browse files Browse the repository at this point in the history
Added drivers command
  • Loading branch information
nbesimi authored Jun 4, 2024
2 parents 5b0e8d1 + 1e5fcb7 commit 12d2d72
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,29 @@ Amanda Wilson,5000,Atlanta,[email protected]
**Note:** When giving a request that will not generate a SELECT statement the query will be generated but will not be executed rather be given to the user to execute on their own.


#### drivers
This command can list drivers that are listed in a `drivers.yaml` file and by choosing a driver you can download it to the `ROSETTA_DRIVERS` directory which will be automatically ready to use.

rosetta drivers [-h, --help] [-f, --file] [--list] <indexToDownload> [-dl, --download]

Parameter | Description
--- | ---
-h, --help | Show the help message and exit.
-f, --file DRIVERS_FILE | YAML drivers file path. If none is supplied it will use drivers.yaml in the current directory and then fallback to our default one.
--list | Used to list all available drivers.
-dl, --download | Used to download selected driver by index.
indexToDownload | Chooses which driver to download depending on the index of the driver.


***Example*** (drivers.yaml)

```yaml
- name: MySQL 8.0.30
link: https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.30.zip
- name: Postgresql 42.3.7
link: https://jdbc.postgresql.org/download/postgresql-42.3.7.jar
```
### Safety Operation
In `model.yaml` you can find the attribute `safeMode` which is by default disabled (false). If you want to prevent any DROP operation during
Expand Down
37 changes: 37 additions & 0 deletions cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.adaptivescale.rosetta.cli;

import com.adaptivescale.rosetta.cli.helpers.DriverHelper;
import com.adaptivescale.rosetta.cli.model.Config;
import com.adaptivescale.rosetta.cli.outputs.DbtSqlModelOutput;
import com.adaptivescale.rosetta.cli.outputs.DbtYamlModelOutput;
import com.adaptivescale.rosetta.cli.outputs.StringOutput;
import com.adaptivescale.rosetta.cli.outputs.YamlModelOutput;
import com.adaptivescale.rosetta.common.models.Database;
import com.adaptivescale.rosetta.common.DriverManagerDriverProvider;
import com.adaptivescale.rosetta.common.models.DriverInfo;
import com.adaptivescale.rosetta.common.models.Table;
import com.adaptivescale.rosetta.common.models.dbt.DbtModel;
import com.adaptivescale.rosetta.common.models.enums.OperationLevelEnum;
Expand Down Expand Up @@ -64,6 +66,7 @@
class Cli implements Callable<Void> {

public static final String DEFAULT_MODEL_YAML = "model.yaml";
public static final String DEFAULT_DRIVERS_YAML = "drivers.yaml";

@CommandLine.Spec
CommandLine.Model.CommandSpec spec;
Expand Down Expand Up @@ -414,6 +417,40 @@ private void diff(@CommandLine.Option(names = {"-s", "--source"}) String sourceN
}
}

@CommandLine.Command(name = "drivers", description = "Show available drivers for download", mixinStandardHelpOptions = true)
private void drivers(@CommandLine.Option(names = {"--list"}, description = "Used to list all available drivers") boolean isList,
@CommandLine.Option(names = {"-dl", "--download"}, description = "Used to download selected driver by index") boolean isDownload,
@CommandLine.Option(names = {"-f", "--file"}, defaultValue = DEFAULT_DRIVERS_YAML) String file,
@CommandLine.Parameters(index = "0", arity = "0..1") Integer driverId) {
Path driversPath = Path.of(file);

if (isList) {
DriverHelper.printDrivers(driversPath);

System.out.println("To download a driver use: rosetta drivers {index} --download");
return;
}

if (isDownload) {
List<DriverInfo> drivers = DriverHelper.getDrivers(driversPath);
if (drivers.isEmpty()) {
System.out.println("No drivers found in the specified YAML file.");
return;
}

if (driverId != null && driverId < 1 || driverId != null && driverId > drivers.size()) {
System.out.println("Invalid index. Please provide a valid index.");
return;
}

DriverHelper.getDriver(driversPath, driverId);
return;
}

// if neither list nor download was called default to help command
spec.subcommands().get("drivers").usage(System.out);
}

private void requireConfig(Config config) {
if (config == null) {
throw new RuntimeException("Config file is required.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.adaptivescale.rosetta.cli.helpers;

import com.adaptivescale.rosetta.common.models.DriverInfo;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.IntStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class DriverHelper {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory());
private static final TypeReference<List<DriverInfo>> DRIVER_INFO_TYPE_REF = new TypeReference<List<DriverInfo>>() {};

/**
* Prints drivers that are downloadable
*
* @param path where to search
*/
public static void printDrivers(Path path) {
List<DriverInfo> drivers = getDrivers(path);

if (drivers.isEmpty()) {
System.out.println("No drivers found.");
return;
}

System.out.println("Downloadable Drivers:");
System.out.println("=====================");

IntStream.range(0, drivers.size()).forEach(index -> {
DriverInfo driverInfo = drivers.get(index);
System.out.printf("%2d: %s%n", index + 1, driverInfo.getName());
});

System.out.println("=====================");
}

/**
* Finds drivers that are downloadable
*
* @param path where to search
* @return List<DriverInfo>
*/
public static List<DriverInfo> getDrivers(Path path) {
try {
if (!Files.exists(path)) {
URL resource = DriverHelper.class.getClassLoader().getResource(path.toString());
if (resource == null) {
throw new RuntimeException("Drivers resource not found: " + path);
}
return OBJECT_MAPPER.readValue(resource, DRIVER_INFO_TYPE_REF);
} else {
return OBJECT_MAPPER.readValue(path.toFile(), DRIVER_INFO_TYPE_REF);
}
} catch (Exception exception) {
throw new RuntimeException("Failed to read drivers from path: " + path, exception);
}
}

/**
* Downloads driver from the selected path by driver index
*
* @param path where to search
* @param driverId which driver to download
* @return DriverInfo
*/
public static DriverInfo getDriver(Path path, Integer driverId) {
try {
List<DriverInfo> drivers = DriverHelper.getDrivers(path);
DriverInfo driverInfo = drivers.get(driverId - 1);
downloadDriver(driverInfo);
return driverInfo;
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}

/**
* Downloads driver to the specified directory
*
* @param driverInfo which driver to download
* @throws RuntimeException if a download error occurs
*/
private static void downloadDriver(DriverInfo driverInfo) {
try {
// Attempt to get the ROSETTA_DRIVERS environment variable
String rosettaDriversPath = System.getenv("ROSETTA_DRIVERS");
if (rosettaDriversPath == null) {
// Fall back to 'drivers' folder one level up if ROSETTA_DRIVERS is not set
rosettaDriversPath = Paths.get("..", "drivers").toString();
}

// Construct the destination directory path
Path rosettaPath = Paths.get(rosettaDriversPath);

// Ensure the directory exists and is writable, or fall back
if (!Files.exists(rosettaPath) || !Files.isWritable(rosettaPath)) {
throw new IllegalArgumentException("No writable directory available for drivers");
}

// Open a connection to the URL of the driver
URL url = new URL(driverInfo.getLink());
// Get the file name from the URL
String fileName = driverInfo.getLink().substring(driverInfo.getLink().lastIndexOf('/') + 1);
// Construct the destination path
Path destination = rosettaPath.resolve(fileName);

// Check if the file already exists
if (Files.exists(destination)) {
System.out.println("Driver already exists: " + destination.toAbsolutePath());
return;
}

// Check if we have write permission to the directory
if (!Files.isWritable(rosettaPath)) {
throw new IOException("No write permission to the directory: " + rosettaPath);
}

// Download the driver file and save it to the specified directory
Files.copy(url.openStream(), destination);
System.out.println("Driver downloaded successfully: " + destination.toAbsolutePath());

// If the downloaded file is a zip file, unzip it
if (fileName.endsWith(".zip")) {
unzipFile(destination, rosettaPath);
// Delete the zip file after extraction
Files.delete(destination);
System.out.println("Zip file extracted and deleted: " + destination.toAbsolutePath());
}

} catch (Exception e) {
System.out.println("Error downloading the driver: " + e.getMessage());
throw new RuntimeException(e);
}
}

/**
* Unzips a zip file to the specified directory
*
* @param zipFilePath the path to the zip file
* @param destDir the destination directory
* @throws IOException if an I/O error occurs
*/
private static void unzipFile(Path zipFilePath, Path destDir) throws IOException {
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
Path entryDestination = destDir.resolve(entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(entryDestination);
} else {
Files.createDirectories(entryDestination.getParent());
Files.copy(zipFile.getInputStream(entry), entryDestination, StandardCopyOption.REPLACE_EXISTING);
}
}
}
}
}
18 changes: 18 additions & 0 deletions cli/src/main/resources/drivers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
- name: Google Cloud BigQuery 4.2
link: https://storage.googleapis.com/simba-bq-release/jdbc/SimbaJDBCDriverforGoogleBigQuery42_1.3.0.1001.zip
- name: Snowflake 3.13.19
link: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/3.13.19/snowflake-jdbc-3.13.19.jar
- name: Postgresql 42.3.7
link: https://jdbc.postgresql.org/download/postgresql-42.3.7.jar
- name: MySQL 8.0.30
link: https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.30.zip
- name: Kinetica 7.1.7.7
link: https://github.com/kineticadb/kinetica-client-jdbc/archive/refs/tags/v7.1.7.7.zip
- name: Google Cloud Spanner 2.6.2
link: https://search.maven.org/remotecontent?filepath=com/google/cloud/google-cloud-spanner-jdbc/2.6.2/google-cloud-spanner-jdbc-2.6.2-single-jar-with-dependencies.jar
- name: SQL Server 12.2.0
link: https://go.microsoft.com/fwlink/?linkid=2223050
- name: DB2 jcc4
link: https://repo1.maven.org/maven2/com/ibm/db2/jcc/db2jcc/db2jcc4/db2jcc-db2jcc4.jar
- name: Oracle 23.2.0.0
link: https://download.oracle.com/otn-pub/otn_software/jdbc/232-DeveloperRel/ojdbc11.jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.adaptivescale.rosetta.common.models;

public class DriverInfo {

private String name;
private String link;

public String getName() {
return name;
}

public String getLink() {
return link;
}
}

0 comments on commit 12d2d72

Please sign in to comment.