Skip to content

Commit

Permalink
[Coral-Service] Graph visualization API (with rewrites) (#454)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinGe00 authored Sep 20, 2023
1 parent db56069 commit 6109508
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 17 deletions.
1 change: 1 addition & 0 deletions coral-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation project(':coral-incremental')
implementation project(':coral-trino')
implementation project(':coral-spark')
implementation project(':coral-visualization')

implementation('org.apache.hive:hive-exec:1.2.2:core') {
exclude group: 'org.apache.calcite', module: 'calcite-core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ public void onApplicationEvent(ContextRefreshedEvent event) {

@PostMapping("/api/translations/translate")
public ResponseEntity translate(@RequestBody TranslateRequestBody translateRequestBody) {
final String fromLanguage = translateRequestBody.getFromLanguage();
final String toLanguage = translateRequestBody.getToLanguage();
final String sourceLanguage = translateRequestBody.getSourceLanguage();
final String targetLanguage = translateRequestBody.getTargetLanguage();
final String query = translateRequestBody.getQuery();

if (fromLanguage.equalsIgnoreCase(toLanguage)) {
if (sourceLanguage.equalsIgnoreCase(targetLanguage)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Please choose different languages to translate between.\n");
}
Expand All @@ -70,36 +70,37 @@ public ResponseEntity translate(@RequestBody TranslateRequestBody translateReque
try {
// TODO: add more translations once n-to-one-to-n is completed
// From Trino
if (fromLanguage.equalsIgnoreCase("trino")) {
if (sourceLanguage.equalsIgnoreCase("trino")) {
// To Spark
if (toLanguage.equalsIgnoreCase("spark")) {
if (targetLanguage.equalsIgnoreCase("spark")) {
translatedSql = translateTrinoToSpark(query);
}
}
// From Hive
else if (fromLanguage.equalsIgnoreCase("hive")) {
else if (sourceLanguage.equalsIgnoreCase("hive")) {
// To Spark
if (toLanguage.equalsIgnoreCase("spark")) {
if (targetLanguage.equalsIgnoreCase("spark")) {
translatedSql = translateHiveToSpark(query);
}
// To Trino
else if (toLanguage.equalsIgnoreCase("trino")) {
else if (targetLanguage.equalsIgnoreCase("trino")) {
translatedSql = translateHiveToTrino(query);
}
}
} catch (Throwable t) {
// TODO: use logger
t.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(t.getMessage());
}

String message;
if (translatedSql == null) {
message = "Translation from " + LANGUAGE_MAP.get(fromLanguage) + " to " + LANGUAGE_MAP.get(toLanguage)
message = "Translation from " + LANGUAGE_MAP.get(sourceLanguage) + " to " + LANGUAGE_MAP.get(targetLanguage)
+ " is not currently supported."
+ " Coral-Service only supports translation from Hive to Trino/Spark, or translation from Trino to Spark.\n";
} else {
message = "Original query in " + LANGUAGE_MAP.get(fromLanguage) + ":\n" + query + "\n" + "Translated to "
+ LANGUAGE_MAP.get(toLanguage) + ":\n" + translatedSql + "\n";
message = "Original query in " + LANGUAGE_MAP.get(sourceLanguage) + ":\n" + query + "\n" + "Translated to "
+ LANGUAGE_MAP.get(targetLanguage) + ":\n" + translatedSql + "\n";
}
return ResponseEntity.status(HttpStatus.OK).body(message);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright 2023 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.coralservice.controller;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.UUID;

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.linkedin.coral.coralservice.entity.VisualizationRequestBody;
import com.linkedin.coral.coralservice.entity.VisualizationResponseBody;
import com.linkedin.coral.coralservice.utils.RewriteType;
import com.linkedin.coral.coralservice.utils.VisualizationUtils;

import static com.linkedin.coral.coralservice.utils.VisualizationUtils.*;


@RestController
@RequestMapping("/api/visualizations")
public class VisualizationController {
private File imageDir = getImageDir();
private VisualizationUtils visualizationUtils = new VisualizationUtils();

@PostMapping("/generategraphs")
public ResponseEntity getIRVisualizations(@RequestBody VisualizationRequestBody visualizationRequestBody) {
final String sourceLanguage = visualizationRequestBody.getSourceLanguage();
final String query = visualizationRequestBody.getQuery();
final RewriteType rewriteType = visualizationRequestBody.getRewriteType();

if (!visualizationUtils.isValidSourceLanguage(sourceLanguage)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Currently, only Hive, Spark, and Trino are supported as engines to generate graphs using.\n");
}

// A list of UUIDs in this order of:
// 1. Image ID of pre/no rewrite relNode
// 2. Image ID of pre/no rewrite sqlNode
// If a rewrite was requested:
// 3. Image ID of post rewrite relNode
// 4. Image ID of post rewrite sqlNode
ArrayList<UUID> imageIdList;
try {
imageIdList = visualizationUtils.generateIRVisualizations(query, sourceLanguage, imageDir, rewriteType);
} catch (Throwable t) {
// TODO: use logger
t.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(t.getMessage());
}

assert imageIdList.size() > 0;

// Build response body
VisualizationResponseBody responseBody = new VisualizationResponseBody();
responseBody.setRelNodeImageID(imageIdList.get(0));
responseBody.setSqlNodeImageID(imageIdList.get(1));
if (imageIdList.size() >= 4) {
// Rewrite was requested
responseBody.setPostRewriteRelNodeImageID(imageIdList.get(2));
responseBody.setPostRewriteSqlNodeImageID(imageIdList.get(3));
}

return ResponseEntity.status(HttpStatus.OK).body(responseBody);
}

@GetMapping("/{imageId}")
public ResponseEntity<FileSystemResource> getImage(@PathVariable String imageId) {
String imagePath = imageDir + File.separator + imageId + ".svg";

if (isValidImage(imagePath)) {
try {
Path path = new File(imagePath).toPath();
String contentType = Files.probeContentType(path);

if (contentType == null) {
contentType = "image/svg+xml";
}
FileSystemResource resource = new FileSystemResource(path);
return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType)).body(resource);

} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(500).build(); // 500 Internal Server Error response
}

} else {
return ResponseEntity.notFound().build();
}
}

private boolean isValidImage(String imagePath) {
// Check if the file exists and is a regular file (not a directory)
File imageFile = new File(imagePath);
return imageFile.exists() && imageFile.isFile();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
package com.linkedin.coral.coralservice.entity;

public class TranslateRequestBody {
private String fromLanguage;
private String toLanguage;
private String sourceLanguage;
private String targetLanguage;
private String query;

public String getFromLanguage() {
return fromLanguage;
public String getSourceLanguage() {
return sourceLanguage;
}

public String getToLanguage() {
return toLanguage;
public String getTargetLanguage() {
return targetLanguage;
}

public String getQuery() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright 2022-2023 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.coralservice.entity;

import com.linkedin.coral.coralservice.utils.RewriteType;


public class VisualizationRequestBody {
private String sourceLanguage;
private String query;

private RewriteType rewriteType;

public String getSourceLanguage() {
return sourceLanguage;
}

public String getQuery() {
return query;
}

public RewriteType getRewriteType() {
return rewriteType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright 2023 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.coralservice.entity;

import java.util.UUID;


public class VisualizationResponseBody {

private UUID sqlNodeImageID;
private UUID relNodeImageID;
private UUID postRewriteSqlNodeImageID;
private UUID postRewriteRelNodeImageID;
public VisualizationResponseBody() {
}

public UUID getSqlNodeImageID() {
return sqlNodeImageID;
}

public UUID getRelNodeImageID() {
return relNodeImageID;
}

public UUID getPostRewriteSqlNodeImageID() {
return postRewriteSqlNodeImageID;
}

public void setPostRewriteSqlNodeImageID(UUID postRewriteSqlNodeImageID) {
this.postRewriteSqlNodeImageID = postRewriteSqlNodeImageID;
}

public void setSqlNodeImageID(UUID sqlNodeImageID) {
this.sqlNodeImageID = sqlNodeImageID;
}

public void setRelNodeImageID(UUID relNodeImageID) {
this.relNodeImageID = relNodeImageID;
}

public UUID getPostRewriteRelNodeImageID() {
return postRewriteRelNodeImageID;
}

public void setPostRewriteRelNodeImageID(UUID postRewriteRelNodeImageID) {
this.postRewriteRelNodeImageID = postRewriteRelNodeImageID;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2023 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.coralservice.utils;

import com.fasterxml.jackson.annotation.JsonCreator;


public enum RewriteType {
NONE("none"),
INCREMENTAL("incremental"),
DATAMASKING("datamasking");

private final String type;

RewriteType(String type) {
this.type = type;
}

@Override
public String toString() {
return type;
}

@JsonCreator
public static RewriteType getDepartmentFromCode(String value) {
for (RewriteType type : RewriteType.values()) {
if (type.toString().equals(value)) {
return type;
}
}
return null;
}

}
Loading

0 comments on commit 6109508

Please sign in to comment.