blob: 43c569bf1fe11934dc6c4d91441047612ee99526 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.calcite.test;
import org.apache.calcite.model.JsonColumn;
import org.apache.calcite.model.JsonCustomSchema;
import org.apache.calcite.model.JsonCustomTable;
import org.apache.calcite.model.JsonJdbcSchema;
import org.apache.calcite.model.JsonLattice;
import org.apache.calcite.model.JsonMapSchema;
import org.apache.calcite.model.JsonRoot;
import org.apache.calcite.model.JsonTable;
import org.apache.calcite.model.JsonTypeAttribute;
import org.apache.calcite.model.JsonView;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Unit test for data models.
*/
class ModelTest {
private ObjectMapper mapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
return mapper;
}
/** Reads a simple schema from a string into objects. */
@Test void testRead() throws IOException {
final ObjectMapper mapper = mapper();
JsonRoot root = mapper.readValue(
"{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " name: 'FoodMart',\n"
+ " types: [\n"
+ " {\n"
+ " name: 'mytype1',\n"
+ " attributes: [\n"
+ " {\n"
+ " name: 'f1',\n"
+ " type: 'BIGINT'\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ],\n"
+ " tables: [\n"
+ " {\n"
+ " name: 'time_by_day',\n"
+ " factory: 'com.test',\n"
+ " columns: [\n"
+ " {\n"
+ " name: 'time_id'\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " name: 'sales_fact_1997',\n"
+ " factory: 'com.test',\n"
+ " columns: [\n"
+ " {\n"
+ " name: 'time_id'\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}",
JsonRoot.class);
assertEquals("1.0", root.version);
assertEquals(1, root.schemas.size());
final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
assertEquals("FoodMart", schema.name);
assertEquals(1, schema.types.size());
final List<JsonTypeAttribute> attributes = schema.types.get(0).attributes;
assertEquals("f1", attributes.get(0).name);
assertEquals("BIGINT", attributes.get(0).type);
assertEquals(2, schema.tables.size());
final JsonTable table0 = schema.tables.get(0);
assertEquals("time_by_day", table0.name);
final JsonTable table1 = schema.tables.get(1);
assertEquals("sales_fact_1997", table1.name);
assertEquals(1, table0.columns.size());
final JsonColumn column = table0.columns.get(0);
assertEquals("time_id", column.name);
}
/** Reads a simple schema containing JdbcSchema, a sub-type of Schema. */
@Test void testSubtype() throws IOException {
final ObjectMapper mapper = mapper();
JsonRoot root = mapper.readValue(
"{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " type: 'jdbc',\n"
+ " name: 'FoodMart',\n"
+ " jdbcUser: 'u_baz',\n"
+ " jdbcPassword: 'p_baz',\n"
+ " jdbcUrl: 'jdbc:baz',\n"
+ " jdbcCatalog: 'cat_baz',\n"
+ " jdbcSchema: ''\n"
+ " }\n"
+ " ]\n"
+ "}",
JsonRoot.class);
assertEquals("1.0", root.version);
assertEquals(1, root.schemas.size());
final JsonJdbcSchema schema = (JsonJdbcSchema) root.schemas.get(0);
assertEquals("FoodMart", schema.name);
}
/** Reads a custom schema. */
@Test void testCustomSchema() throws IOException {
final ObjectMapper mapper = mapper();
JsonRoot root = mapper.readValue("{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " type: 'custom',\n"
+ " name: 'My Custom Schema',\n"
+ " factory: 'com.acme.MySchemaFactory',\n"
+ " operand: {a: 'foo', b: [1, 3.5] },\n"
+ " tables: [\n"
+ " { type: 'custom', name: 'T1', factory: 'com.test' },\n"
+ " { type: 'custom', name: 'T2', factory: 'com.test', operand: {} },\n"
+ " { type: 'custom', name: 'T3', factory: 'com.test', operand: {a: 'foo'} }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " type: 'custom',\n"
+ " factory: 'com.acme.MySchemaFactory',\n"
+ " name: 'has-no-operand'\n"
+ " }\n"
+ " ]\n"
+ "}",
JsonRoot.class);
assertEquals("1.0", root.version);
assertEquals(2, root.schemas.size());
final JsonCustomSchema schema = (JsonCustomSchema) root.schemas.get(0);
assertEquals("My Custom Schema", schema.name);
assertEquals("com.acme.MySchemaFactory", schema.factory);
assertEquals("foo", schema.operand.get("a"));
assertNull(schema.operand.get("c"));
assertTrue(schema.operand.get("b") instanceof List);
final List list = (List) schema.operand.get("b");
assertEquals(2, list.size());
assertEquals(1, list.get(0));
assertEquals(3.5, list.get(1));
assertEquals(3, schema.tables.size());
assertNull(((JsonCustomTable) schema.tables.get(0)).operand);
assertTrue(((JsonCustomTable) schema.tables.get(1)).operand.isEmpty());
}
/** Tests that an immutable schema in a model cannot contain a
* materialization. */
@Test void testModelImmutableSchemaCannotContainMaterialization()
throws Exception {
CalciteAssert.model("{\n"
+ " version: '1.0',\n"
+ " defaultSchema: 'adhoc',\n"
+ " schemas: [\n"
+ " {\n"
+ " name: 'empty'\n"
+ " },\n"
+ " {\n"
+ " name: 'adhoc',\n"
+ " type: 'custom',\n"
+ " factory: '"
+ JdbcTest.MySchemaFactory.class.getName()
+ "',\n"
+ " operand: {\n"
+ " 'tableName': 'ELVIS',\n"
+ " 'mutable': false\n"
+ " },\n"
+ " materializations: [\n"
+ " {\n"
+ " table: 'v',\n"
+ " sql: 'values (1)'\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}")
.connectThrows("Cannot define materialization; parent schema 'adhoc' "
+ "is not a SemiMutableSchema");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1899">[CALCITE-1899]
* When reading model, give error if mandatory JSON attributes are
* missing</a>.
*
* <p>Schema without name should give useful error, not
* NullPointerException. */
@Test void testSchemaWithoutName() throws Exception {
final String model = "{\n"
+ " version: '1.0',\n"
+ " defaultSchema: 'adhoc',\n"
+ " schemas: [ {\n"
+ " } ]\n"
+ "}";
CalciteAssert.model(model)
.connectThrows("Missing required creator property 'name'");
}
@Test void testCustomSchemaWithoutFactory() throws Exception {
final String model = "{\n"
+ " version: '1.0',\n"
+ " defaultSchema: 'adhoc',\n"
+ " schemas: [ {\n"
+ " type: 'custom',\n"
+ " name: 'my_custom_schema'\n"
+ " } ]\n"
+ "}";
CalciteAssert.model(model)
.connectThrows("Missing required creator property 'factory'");
}
/** Tests a model containing a lattice and some views. */
@Test void testReadLattice() throws IOException {
final ObjectMapper mapper = mapper();
JsonRoot root = mapper.readValue("{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " name: 'FoodMart',\n"
+ " tables: [\n"
+ " {\n"
+ " name: 'time_by_day',\n"
+ " factory: 'com.test',\n"
+ " columns: [\n"
+ " {\n"
+ " name: 'time_id'\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " name: 'sales_fact_1997',\n"
+ " factory: 'com.test',\n"
+ " columns: [\n"
+ " {\n"
+ " name: 'time_id'\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " name: 'V',\n"
+ " type: 'view',\n"
+ " sql: 'values (1)'\n"
+ " },\n"
+ " {\n"
+ " name: 'V2',\n"
+ " type: 'view',\n"
+ " sql: [ 'values (1)', '(2)' ]\n"
+ " }\n"
+ " ],\n"
+ " lattices: [\n"
+ " {\n"
+ " name: 'SalesStar',\n"
+ " sql: 'select * from sales_fact_1997'\n"
+ " },\n"
+ " {\n"
+ " name: 'SalesStar2',\n"
+ " sql: [ 'select *', 'from sales_fact_1997' ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}",
JsonRoot.class);
assertEquals("1.0", root.version);
assertEquals(1, root.schemas.size());
final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
assertEquals("FoodMart", schema.name);
assertEquals(2, schema.lattices.size());
final JsonLattice lattice0 = schema.lattices.get(0);
assertEquals("SalesStar", lattice0.name);
assertEquals("select * from sales_fact_1997", lattice0.getSql());
final JsonLattice lattice1 = schema.lattices.get(1);
assertEquals("SalesStar2", lattice1.name);
assertEquals("select *\nfrom sales_fact_1997\n", lattice1.getSql());
assertEquals(4, schema.tables.size());
final JsonTable table1 = schema.tables.get(1);
assertTrue(!(table1 instanceof JsonView));
final JsonTable table2 = schema.tables.get(2);
assertTrue(table2 instanceof JsonView);
assertThat(((JsonView) table2).getSql(), equalTo("values (1)"));
final JsonTable table3 = schema.tables.get(3);
assertTrue(table3 instanceof JsonView);
assertThat(((JsonView) table3).getSql(), equalTo("values (1)\n(2)\n"));
}
/** Tests a model with bad multi-line SQL. */
@Test void testReadBadMultiLineSql() throws IOException {
final ObjectMapper mapper = mapper();
JsonRoot root = mapper.readValue("{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " name: 'FoodMart',\n"
+ " tables: [\n"
+ " {\n"
+ " name: 'V',\n"
+ " type: 'view',\n"
+ " sql: [ 'values (1)', 2 ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}",
JsonRoot.class);
assertEquals(1, root.schemas.size());
final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
assertEquals(1, schema.tables.size());
final JsonView table1 = (JsonView) schema.tables.get(0);
try {
String s = table1.getSql();
fail("expected error, got " + s);
} catch (RuntimeException e) {
assertThat(e.getMessage(),
equalTo("each element of a string list must be a string; found: 2"));
}
}
@Test void testYamlInlineDetection() throws Exception {
// yaml model with different line endings
final String yamlModel = "version: 1.0\r\n"
+ "schemas:\n"
+ "- type: custom\r\n"
+ " name: 'MyCustomSchema'\n"
+ " factory: " + JdbcTest.MySchemaFactory.class.getName() + "\r\n";
CalciteAssert.model(yamlModel).doWithConnection(calciteConnection -> null);
// with a comment
CalciteAssert.model("\n \r\n# comment\n " + yamlModel)
.doWithConnection(calciteConnection -> null);
// if starts with { => treated as json
CalciteAssert.model(" { " + yamlModel + " }")
.connectThrows("Unexpected character ('s' (code 115)): "
+ "was expecting comma to separate Object entries");
// if starts with /* => treated as json
CalciteAssert.model(" /* " + yamlModel)
.connectThrows("Unexpected end-of-input in a comment");
}
@Test void testYamlFileDetection() throws Exception {
final URL inUrl = ModelTest.class.getResource("/empty-model.yaml");
CalciteAssert.that()
.withModel(inUrl)
.doWithConnection(calciteConnection -> null);
}
}