/* * 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.drill.exec.store.mock; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Table; import org.apache.drill.common.JSONOptions; import org.apache.drill.common.expression.SchemaPath; import org.apache.drill.common.logical.StoragePluginConfig; import org.apache.drill.exec.physical.base.AbstractGroupScan; import org.apache.drill.exec.planner.logical.DynamicDrillTable; import org.apache.drill.exec.server.DrillbitContext; import org.apache.drill.exec.store.AbstractSchema; import org.apache.drill.exec.store.AbstractStoragePlugin; import org.apache.drill.exec.store.SchemaConfig; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; public class MockStorageEngine extends AbstractStoragePlugin { static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MockStorageEngine.class); private final MockStorageEngineConfig configuration; private final MockSchema schema; public MockStorageEngine(MockStorageEngineConfig configuration, DrillbitContext context, String name) { this.configuration = configuration; this.schema = new MockSchema(this); } @Override public AbstractGroupScan getPhysicalScan(String userName, JSONOptions selection, List<SchemaPath> columns) throws IOException { List<MockTableDef.MockScanEntry> readEntries = selection.getListWith(new ObjectMapper(), new TypeReference<ArrayList<MockTableDef.MockScanEntry>>() { }); assert ! readEntries.isEmpty(); return new MockGroupScanPOP(null, readEntries); } @Override public void registerSchemas(SchemaConfig schemaConfig, SchemaPlus parent) throws IOException { parent.add(schema.getName(), schema); } @Override public StoragePluginConfig getConfig() { return configuration; } @Override public boolean supportsRead() { return true; } /** * Resolves table names within the mock data source. Tables can be of two forms: * <p> * <tt><name>_<n><unit></tt> * <p> * Where the "name" can be anything, "n" is the number of rows, and "unit" is * the units for the row count: non, K (thousand) or M (million). * <p> * The above form generates a table directly with no other information needed. * Column names must be provided, and must be of the form: * <p> * <tt><name>_<type><size></tt> * <p> * Where the name can be anything, the type must be i (integer), d (double), * b (boolean) * or s (string, AKA VarChar). The length is needed only for string fields. * <p> * Direct tables are quick, but limited. The other option is to provide the * name of a definition file: * <p> * <tt><fileName>.json</tt> * <p> * In this case, the JSON file must be a resource visible on the class path. * Omit the leading slash in the resource path name. */ private static class MockSchema extends AbstractSchema { private MockStorageEngine engine; public MockSchema(MockStorageEngine engine) { super(ImmutableList.<String>of(), MockStorageEngineConfig.NAME); this.engine = engine; } @Override public Table getTable(String name) { if (name.toLowerCase().endsWith(".json")) { return getConfigFile(name); } else { return getDirectTable(name); } } private Table getConfigFile(String name) { final URL url = Resources.getResource(name); if (url == null) { throw new IllegalArgumentException( "Unable to find mock table config file " + name); } MockTableDef mockTableDefn; try { String json = Resources.toString(url, Charsets.UTF_8); final ObjectMapper mapper = new ObjectMapper(); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mockTableDefn = mapper.readValue(json, MockTableDef.class); } catch (JsonParseException e) { throw new IllegalArgumentException("Unable to parse mock table definition file: " + name, e); } catch (JsonMappingException e) { throw new IllegalArgumentException("Unable to Jackson deserialize mock table definition file: " + name, e); } catch (IOException e) { throw new IllegalArgumentException("Unable to read mock table definition file: " + name, e); } return new DynamicDrillTable(engine, this.name, mockTableDefn.getEntries()); } private Table getDirectTable(String name) { Pattern p = Pattern.compile("(\\w+)_(\\d+)(k|m)?", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(name); if (! m.matches()) { return null; } @SuppressWarnings("unused") String baseName = m.group(1); int n = Integer.parseInt(m.group(2)); String unit = m.group(3); if (unit == null) { } else if (unit.equalsIgnoreCase("K")) { n *= 1000; } else if (unit.equalsIgnoreCase("M")) { n *= 1_000_000; } MockTableDef.MockScanEntry entry = new MockTableDef.MockScanEntry(n, true, 0, 1, null); List<MockTableDef.MockScanEntry> list = new ArrayList<>(); list.add(entry); return new DynamicDrillTable(engine, this.name, list); } @Override public Set<String> getTableNames() { return new HashSet<>(); } @Override public String getTypeName() { return MockStorageEngineConfig.NAME; } } }