/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you 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 net.hydromatic.optiq.model;
import net.hydromatic.optiq.*;
import net.hydromatic.optiq.impl.*;
import net.hydromatic.optiq.impl.jdbc.JdbcSchema;
import net.hydromatic.optiq.jdbc.OptiqConnection;
import net.hydromatic.optiq.jdbc.OptiqSchema;
import org.eigenbase.util.Pair;
import org.eigenbase.util.Util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import javax.sql.DataSource;
import static org.eigenbase.util.Stacks.*;
/**
* Reads a model and creates schema objects accordingly.
*/
public class ModelHandler {
private final OptiqConnection connection;
private final List<Pair<String, SchemaPlus>> schemaStack =
new ArrayList<Pair<String, SchemaPlus>>();
public ModelHandler(OptiqConnection connection, String uri)
throws IOException {
super();
this.connection = connection;
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
JsonRoot root;
if (uri.startsWith("inline:")) {
root = mapper.readValue(
uri.substring("inline:".length()), JsonRoot.class);
} else {
root = mapper.readValue(new File(uri), JsonRoot.class);
}
visit(root);
}
/** Creates and validates a ScalarFunctionImpl. */
public static Function create(List<String> path, String className) {
final Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("UDF class '"
+ className + "' not found");
}
// Must look for TableMacro before ScalarFunction. Both have an "eval"
// method.
final TableFunction tableFunction = TableFunctionImpl.create(clazz);
if (tableFunction != null) {
return tableFunction;
}
final TableMacro macro = TableMacroImpl.create(clazz);
if (macro != null) {
return macro;
}
final ScalarFunction function = ScalarFunctionImpl.create(clazz);
if (function != null) {
return function;
}
final AggregateFunction aggFunction = AggregateFunctionImpl.create(clazz);
if (aggFunction != null) {
return aggFunction;
}
throw new RuntimeException("Not a valid function class: " + clazz
+ ". Scalar functions and table macros have an 'eval' method; "
+ "aggregate functions have 'init' and 'add' methods, and optionally "
+ "'initAdd', 'merge' and 'result' methods.");
}
public void visit(JsonRoot root) {
final Pair<String, SchemaPlus> pair =
Pair.of(null, connection.getRootSchema());
push(schemaStack, pair);
for (JsonSchema schema : root.schemas) {
schema.accept(this);
}
pop(schemaStack, pair);
if (root.defaultSchema != null) {
try {
connection.setSchema(root.defaultSchema);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public void visit(JsonMapSchema jsonSchema) {
final SchemaPlus parentSchema = currentMutableSchema("schema");
final SchemaPlus schema =
parentSchema.add(jsonSchema.name, new AbstractSchema());
if (jsonSchema.path != null) {
schema.setPath(stringListList(jsonSchema.path));
}
populateSchema(jsonSchema, schema);
if (schema.getName().equals("mat")) {
// Inject by hand a Star Table. Later we'll add a JSON model element.
final List<Table> tables = new ArrayList<Table>();
final String[] tableNames = {
"sales_fact_1997", "time_by_day", "product", "product_class"
};
final SchemaPlus schema2 = parentSchema.getSubSchema("foodmart");
for (String tableName : tableNames) {
tables.add(schema2.getTable(tableName));
}
final String tableName = "star";
schema.add(tableName, StarTable.of(tables));
}
}
private static ImmutableList<ImmutableList<String>> stringListList(
List path) {
final ImmutableList.Builder<ImmutableList<String>> builder =
ImmutableList.builder();
for (Object s : path) {
builder.add(stringList(s));
}
return builder.build();
}
private static ImmutableList<String> stringList(Object s) {
if (s instanceof String) {
return ImmutableList.of((String) s);
} else if (s instanceof List) {
final ImmutableList.Builder<String> builder2 =
ImmutableList.builder();
for (Object o : (List) s) {
if (o instanceof String) {
builder2.add((String) o);
} else {
throw new RuntimeException("Invalid path element " + o
+ "; was expecting string");
}
}
return builder2.build();
} else {
throw new RuntimeException("Invalid path element " + s
+ "; was expecting string or list of string");
}
}
private void populateSchema(JsonSchema jsonSchema, SchemaPlus schema) {
boolean cache = jsonSchema.cache == null || jsonSchema.cache;
schema.setCacheEnabled(cache);
final Pair<String, SchemaPlus> pair = Pair.of(jsonSchema.name, schema);
push(schemaStack, pair);
jsonSchema.visitChildren(this);
pop(schemaStack, pair);
}
public void visit(JsonCustomSchema jsonSchema) {
try {
final SchemaPlus parentSchema = currentMutableSchema("sub-schema");
final Class clazz = Class.forName(jsonSchema.factory);
final SchemaFactory schemaFactory = (SchemaFactory) clazz.newInstance();
final Schema schema =
schemaFactory.create(
parentSchema, jsonSchema.name, jsonSchema.operand);
final SchemaPlus optiqSchema = parentSchema.add(jsonSchema.name, schema);
populateSchema(jsonSchema, optiqSchema);
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonSchema, e);
}
}
public void visit(JsonJdbcSchema jsonSchema) {
final SchemaPlus parentSchema = currentMutableSchema("jdbc schema");
final DataSource dataSource =
JdbcSchema.dataSource(jsonSchema.jdbcUrl,
jsonSchema.jdbcDriver,
jsonSchema.jdbcUser,
jsonSchema.jdbcPassword);
JdbcSchema schema =
JdbcSchema.create(parentSchema, jsonSchema.name, dataSource,
jsonSchema.jdbcCatalog, jsonSchema.jdbcSchema);
final SchemaPlus optiqSchema = parentSchema.add(jsonSchema.name, schema);
populateSchema(jsonSchema, optiqSchema);
}
public void visit(JsonMaterialization jsonMaterialization) {
try {
final SchemaPlus schema = currentSchema();
if (!schema.isMutable()) {
throw new RuntimeException(
"Cannot define materialization; parent schema '"
+ currentSchemaName()
+ "' is not a SemiMutableSchema");
}
OptiqSchema optiqSchema = OptiqSchema.from(schema);
schema.add(jsonMaterialization.view,
MaterializedViewTable.create(
optiqSchema, jsonMaterialization.sql, null,
jsonMaterialization.table));
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonMaterialization,
e);
}
}
public void visit(JsonCustomTable jsonTable) {
try {
final SchemaPlus schema = currentMutableSchema("table");
final Class clazz = Class.forName(jsonTable.factory);
final TableFactory tableFactory = (TableFactory) clazz.newInstance();
final Table table =
tableFactory.create(schema, jsonTable.name, jsonTable.operand, null);
schema.add(jsonTable.name, table);
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonTable, e);
}
}
public void visit(JsonView jsonView) {
try {
final SchemaPlus schema = currentMutableSchema("view");
final List<String> path = Util.first(jsonView.path, currentSchemaPath());
schema.add(jsonView.name,
ViewTable.viewMacro(schema, jsonView.sql, path));
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonView, e);
}
}
private List<String> currentSchemaPath() {
return Collections.singletonList(peek(schemaStack).left);
}
private SchemaPlus currentSchema() {
return peek(schemaStack).right;
}
private String currentSchemaName() {
return peek(schemaStack).left;
}
private SchemaPlus currentMutableSchema(String elementType) {
final SchemaPlus schema = currentSchema();
if (!schema.isMutable()) {
throw new RuntimeException(
"Cannot define " + elementType + "; parent schema '"
+ schema.getName() + "' is not mutable");
}
return schema;
}
public void visit(JsonFunction jsonFunction) {
try {
final SchemaPlus schema = currentMutableSchema("function");
final List<String> path =
Util.first(jsonFunction.path, currentSchemaPath());
schema.add(jsonFunction.name,
create(path, jsonFunction.className));
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonFunction, e);
}
}
}
// End ModelHandler.java