/* * Copyright 2014 TWO SIGMA OPEN SOURCE, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.twosigma.beaker.sql; import com.twosigma.beaker.NamespaceClient; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; public class BeakerParser { public static final String DB_URI_VAR = "beakerDB"; public static final String INPUTS_VAR = "inputs"; public static final String OUTPUTS_VAR = "outputs"; public static final String SQL_SELECT = "SELECT"; public static final String SQL_FROM = "FROM"; public static final String SQL_INTO = "INTO"; public static final String VAR_VALUE_START = "${"; public static final String VAR_VALUE_END = "}"; protected final JDBCClient jdbcClient; private NamespaceClient client; private ConnectionStringHolder dbURI; private Map<String, String> inputs = new HashMap<>(); private Set<String> outputs = new HashSet<>(); private Map<String, ConnectionStringHolder> namedConnectionString; private ConnectionStringHolder defaultConnectionString; private List<BeakerParseResult> results = new ArrayList<>(); public BeakerParser(String script, NamespaceClient client, ConnectionStringHolder defaultConnectionString, Map<String, ConnectionStringHolder> namedConnectionString, JDBCClient jdbcClient) throws IOException, DBConnectionException { this.client = client; this.jdbcClient = jdbcClient; this.defaultConnectionString = defaultConnectionString; this.namedConnectionString = namedConnectionString; parseVar(script); List<String> queries = QueryParser.split(script); if (queries != null && !queries.isEmpty()) { for (String query : queries) { BeakerParseResult result = new BeakerParseResult(query); parseSelectInto(result); parseInputParam(result); results.add(result); } } } private void parseInputParam(BeakerParseResult result) { String sql = result.getResultQuery(); String upper = sql.toUpperCase(); int start = -1; int end = -1; do { start = upper.indexOf(VAR_VALUE_START, end); if (start >= 0) { end = upper.indexOf(VAR_VALUE_END, start); if (end < 0) break; String var = sql.substring(start + 2, end).trim(); if (var != null && !var.isEmpty()) { sql = sql.substring(0, start) + "?" + sql.substring(end + 1); upper = sql.toUpperCase(); end = start + 1; BeakerInputVar inputVar = new BeakerInputVar(var); inputVar.setType(inputs.get(var)); result.getInputVars().add(inputVar); } } } while (start >= 0); result.setResultQuery(sql); } private void parseSelectInto(BeakerParseResult result) { String sql = result.getResultQuery(); String upper = sql.toUpperCase(); int select = -1; int from = -1; int into = -1; do { select = from; select = upper.indexOf(SQL_SELECT, select); if (select >= 0) { from = upper.indexOf(SQL_FROM, select); if (from < 0) break; into = upper.indexOf(SQL_INTO, select); if (into > select && into < from) { int start = upper.indexOf(VAR_VALUE_START, into); if (start > into && start < from) { int end = upper.indexOf(VAR_VALUE_END, into); if (end > into && end < from) { String var = sql.substring(start + 2, end); sql = sql.substring(0, into) + sql.substring(end + 1); upper = sql.toUpperCase(); from = select; if (var != null && !var.isEmpty()) { result.setSelectInto(true); result.setSelectIntoVar(var); } } } } } } while (select >= 0); result.setResultQuery(sql); } private void parseVar(String script) throws IOException, DBConnectionException { List<String> vars = new ArrayList<>(); Scanner scanner = new Scanner(script); StringBuffer sb = new StringBuffer(); while (scanner.hasNextLine()) { String line = scanner.nextLine(); line.trim(); int commentIndex = line.indexOf("%%"); if (commentIndex != -1 && line.startsWith("%%")) { vars.add(line); if (line.indexOf(DB_URI_VAR) > 0) { String value = line.substring(line.indexOf('=') + 1).trim(); int start = value.indexOf(VAR_VALUE_START); int end = value.indexOf(VAR_VALUE_END, start); if (value.startsWith("\"") && value.endsWith("\"")) { dbURI = new ConnectionStringHolder(value.substring(1, value.length() - 1), jdbcClient); } else if (start >= 0 && end > 0) { String var = value.substring(start + 2, end).trim(); dbURI = new ConnectionStringHolder(client.get(var).toString(), jdbcClient); } else { dbURI = namedConnectionString.get(value); if (dbURI == null) throw new DBConnectionException(value, new SQLException("Named connection witn name " + value + " not found")); } } else if (line.indexOf(OUTPUTS_VAR) > 0) { String outLine = line.substring(line.indexOf(':') + 1).trim(); for (String out : outLine.split(";")) { outputs.add(out.trim()); } } else if (line.indexOf(INPUTS_VAR) > 0) { String inLine = line.substring(line.indexOf(':') + 1).trim(); for (String in : inLine.split(";")) { int d = in.indexOf('/'); if (d > 0) { String var = in.substring(0, d).trim(); String type = in.substring(d + 1, in.length()).trim(); inputs.put(var, type); } else inputs.put(in.trim(), null); } } } } if (dbURI == null) dbURI = defaultConnectionString; } public ConnectionStringHolder getDbURI() { return dbURI; } public List<BeakerParseResult> getResults() { return results; } public void setResults(List<BeakerParseResult> results) { this.results = results; } public Map<String, String> getInputs() { return inputs; } public void setInputs(Map<String, String> inputs) { this.inputs = inputs; } public Set<String> getOutputs() { return outputs; } public void setOutputs(Set<String> outputs) { this.outputs = outputs; } }