Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum DBMSType {
HIVE("Apache Hive", "hive"),
SPARK("Spark", "spark"),
SNOWFLAKE("Snowflake", "snowflake"),
SYNAPSE("Azure Synapse", "synapse");
SYNAPSE("Azure Synapse", "synapse"),
TREXSQL("TrexSQL", "trexsql");

private String label;
// For further pass into SqlRender.translateSql as "targetDialect" and DatabaseConnector as "dbms"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private static ConnectionStringParser getParser(DBMSType dbmsType) {
case PDW:
case SNOWFLAKE:
case SYNAPSE:
case TREXSQL:
return new GenericParser();
case REDSHIFT:
return new RedshiftParser();
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/ohdsi/webapi/common/DBMSType.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum DBMSType {
HIVE("Apache Hive", "hive"),
SPARK("Spark", "spark"),
SNOWFLAKE("Snowflake", "snowflake"),
SYNAPSE("Azure Synapse", "synapse");
SYNAPSE("Azure Synapse", "synapse"),
TREXSQL("TrexSQL", "trexsql");

private String label;
// For further pass into SqlRender.translateSql as "targetDialect" and DatabaseConnector as "dbms"
Expand Down
86 changes: 86 additions & 0 deletions src/main/java/org/ohdsi/webapi/source/TrexsqlDialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.ohdsi.webapi.source;

import org.ohdsi.sql.SqlTranslate;
import org.ohdsi.webapi.common.DBMSType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
* Registers "trexsql" as a SqlRender target dialect.
*
* SqlRender has no trexsql target of its own, so its patterns for the engine
* trexsql is built on are copied under the trexsql name. SqlTranslate keeps a
* single JVM-wide pattern table, so this also covers libraries that translate
* internally (cohort and characterization SQL).
*/
@Component
public class TrexsqlDialect {

public static final String NAME = DBMSType.TREXSQL.getOhdsiDB();

// The SqlRender target whose patterns trexsql reuses.
static final String BASE_DIALECT = "duckdb";

private static final String PATTERNS_RESOURCE = "/inst/csv/replacementPatterns.csv";
private static final Logger log = LoggerFactory.getLogger(TrexsqlDialect.class);

private static volatile boolean registered;

@PostConstruct
void init() {
register();
}

public static synchronized void register() {
if (registered) {
return;
}
List<String> lines = readBundledPatterns();
List<String> merged = new ArrayList<>(lines);
String prefix = BASE_DIALECT + ",";
for (String line : lines) {
if (line.startsWith(prefix)) {
merged.add(NAME + line.substring(BASE_DIALECT.length()));
}
}
if (merged.size() == lines.size()) {
throw new IllegalStateException("SqlRender has no " + BASE_DIALECT + " patterns to register " + NAME + " from");
}
try {
Path file = Files.createTempFile("sqlrender-patterns-", ".csv");
file.toFile().deleteOnExit();
Files.write(file, merged, StandardCharsets.UTF_8);
SqlTranslate.setReplacementPatterns(file.toString());
} catch (IOException e) {
throw new UncheckedIOException("Could not register the " + NAME + " SqlRender dialect", e);
}
registered = true;
log.info("Registered SqlRender dialect {} ({} patterns)", NAME, merged.size() - lines.size());
}

private static List<String> readBundledPatterns() {
try (InputStream in = SqlTranslate.class.getResourceAsStream(PATTERNS_RESOURCE)) {
if (in == null) {
throw new IllegalStateException("SqlRender resource " + PATTERNS_RESOURCE + " not found");
}
return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
.lines().collect(Collectors.toList());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
42 changes: 42 additions & 0 deletions src/test/java/org/ohdsi/webapi/source/TrexsqlDialectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.ohdsi.webapi.source;

import org.junit.BeforeClass;
import org.junit.Test;
import org.ohdsi.sql.SqlTranslate;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

public class TrexsqlDialectTest {

private static final String SQL =
"SELECT TOP 10 person_id, DATEADD(day, 30, observation_period_start_date) AS d "
+ "INTO #codesets FROM @cdm.observation_period WHERE ISNULL(period_type_concept_id, 0) > 0;";

@BeforeClass
public static void registerDialect() {
TrexsqlDialect.register();
}

@Test
public void translatesLikeItsBaseDialect() {
String trexsql = SqlTranslate.translateSql(SQL, TrexsqlDialect.NAME);
assertEquals(SqlTranslate.translateSql(SQL, TrexsqlDialect.BASE_DIALECT), trexsql);
assertNotEquals(SQL, trexsql);
}

@Test
public void registerIsIdempotent() {
TrexsqlDialect.register();
assertEquals(
SqlTranslate.translateSql(SQL, TrexsqlDialect.BASE_DIALECT),
SqlTranslate.translateSql(SQL, TrexsqlDialect.NAME));
}

@Test
public void otherDialectsAreUnchanged() {
assertNotEquals(
SqlTranslate.translateSql(SQL, "postgresql"),
SqlTranslate.translateSql(SQL, TrexsqlDialect.NAME));
}
}
18 changes: 17 additions & 1 deletion src/test/java/org/ohdsi/webapi/util/DataSourceDTOParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class DataSourceDTOParserTest {
public static final String ORACLE_WITH_PWD_CONN_STR = "jdbc:oracle:thin:scott/tiger@myhost:1521:orcl";
public static final String HIVE_CONN_STR = "jdbc:hive2://sandbox-hdp.hortonworks.com:2181/synpuf_531_orc;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2";
public static final String IRIS_CONN_STR = "jdbc:IRIS://localhost:1972/USER";
public static final String TREXSQL_CONN_STR = "jdbc:trexsql://trex.example.com:9494";

@Test
public void parseDTO() {
Expand Down Expand Up @@ -112,6 +113,12 @@ public void parseDTO() {
assertThat(dto.getUsername(), is(nullValue()));
assertThat(dto.getPassword(), is(nullValue()));

dto = DataSourceDTOParser.parseDTO(getTrexsqlSource());
assertThat(dto.getType(), is(DBMSType.TREXSQL));
assertThat(dto.getConnectionString(), is(TREXSQL_CONN_STR));
assertThat(dto.getUsername(), is("webapi"));
assertThat(dto.getPassword(), is("access-token"));

// IRIS is not supported in DBMSType enum, commenting out
// dto = DataSourceDTOParser.parseDTO(getIRISSource());
// assertThat(dto.getType(), is(DBMSType.IRIS));
Expand Down Expand Up @@ -219,4 +226,13 @@ private Source getIRISSource() {
source.setSourceConnection(IRIS_CONN_STR);
return source;
}
}

private Source getTrexsqlSource() {
Source source = new Source();
source.setSourceDialect("trexsql");
source.setSourceConnection(TREXSQL_CONN_STR);
source.setUsername("webapi");
source.setPassword("access-token");
return source;
}
}
Loading