github-code-scanning[bot] commented on code in PR #13627: URL: https://github.com/apache/druid/pull/13627#discussion_r1061149392
########## server/src/main/java/org/apache/druid/catalog/model/table/InputSourceDefn.java: ########## @@ -0,0 +1,100 @@ +/* + * 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.druid.catalog.model.table; + +import org.apache.druid.catalog.model.TableDefnRegistry; + +/** + * Metadata definition for one Druid input source. + * + * @see {@link ExternalTableDefn} for a detailed explanation. + */ +public interface InputSourceDefn +{ + /** + * Gather information about the set of format definitions. + */ + void bind(TableDefnRegistry registry); + + /** + * Type value for this format: same as the type string used in the serialized + * JSON for this input source. Used as the key for this definition within the + * table registry, and associates the serialized JSON with the corresponding + * input source definition. + */ + String typeValue(); + + /** + * Given a external table catalog spec, with the JSON input source and format + * properties parsed to generic Java maps, validate that the properties are + * valid prior to saving the spec into the catalog. + * + * @param table a catalog table spec with the input source and input format Review Comment: ## Spurious Javadoc @param tags @param tag "table" does not match any actual parameter of method "validate()". [Show more details](https://github.com/apache/druid/security/code-scanning/3589) ########## sql/src/main/java/org/apache/druid/sql/calcite/external/ExternalOperatorConversion.java: ########## @@ -19,95 +19,112 @@ package org.apache.druid.sql.calcite.external; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Inject; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlIdentifier; -import org.apache.calcite.sql.SqlOperator; -import org.apache.calcite.sql.parser.SqlParserPos; -import org.apache.calcite.sql.type.OperandTypes; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.type.SqlTypeFamily; -import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro; +import org.apache.druid.catalog.model.CatalogUtils; +import org.apache.druid.catalog.model.ColumnSpec; +import org.apache.druid.catalog.model.Columns; +import org.apache.druid.catalog.model.table.BaseTableFunction; +import org.apache.druid.catalog.model.table.ExternalTableSpec; +import org.apache.druid.data.input.InputFormat; +import org.apache.druid.data.input.InputSource; import org.apache.druid.guice.annotations.Json; +import org.apache.druid.java.util.common.IAE; import org.apache.druid.segment.column.RowSignature; -import org.apache.druid.server.security.Action; -import org.apache.druid.server.security.Resource; -import org.apache.druid.server.security.ResourceAction; -import org.apache.druid.server.security.ResourceType; -import org.apache.druid.sql.calcite.expression.AuthorizableOperator; -import org.apache.druid.sql.calcite.expression.DruidExpression; -import org.apache.druid.sql.calcite.expression.SqlOperatorConversion; -import org.apache.druid.sql.calcite.planner.DruidTypeSystem; -import org.apache.druid.sql.calcite.planner.PlannerContext; -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Arrays; +import java.util.List; +import java.util.Map; /** * Registers the "EXTERN" operator, which is used in queries like - * "INSERT INTO dst SELECT * FROM TABLE(EXTERN(...))". + * <pre>{@code + * INSERT INTO dst SELECT * FROM TABLE(EXTERN( + * "<input source>", + * "<input format>", + * "<signature>")) * - * This class is exercised in CalciteInsertDmlTest but is not currently exposed to end users. + * INSERT INTO dst SELECT * FROM TABLE(EXTERN( + * inputSource => "<input source>", + * inputFormat => "<input format>")) + * EXTEND (<columns>) + * }</pre> + * Where either the by-position or by-name forms are usable with either + * a Druid JSON signature, or an SQL {@code EXTEND} list of columns. + * As with all table functions, the {@code EXTEND} is optional. */ -public class ExternalOperatorConversion implements SqlOperatorConversion +public class ExternalOperatorConversion extends CatalogExternalTableOperatorConversion { public static final String FUNCTION_NAME = "EXTERN"; - // Resource that allows reading external data via SQL. - public static final ResourceAction EXTERNAL_RESOURCE_ACTION = - new ResourceAction(new Resource("EXTERNAL", ResourceType.EXTERNAL), Action.READ); + public static final String INPUT_SOURCE_PARAM = "inputSource"; + public static final String INPUT_FORMAT_PARAM = "inputFormat"; + public static final String SIGNATURE_PARAM = "signature"; - private final SqlUserDefinedTableMacro operator; - - @Inject - public ExternalOperatorConversion(@Json final ObjectMapper jsonMapper) - { - this.operator = new ExternalOperator(new ExternalTableMacro(jsonMapper)); - } - - @Override - public SqlOperator calciteOperator() + /** + * The use of a table function allows the use of optional arguments, + * so that the signature can be given either as the original-style + * serialized JSON signature, or the updated SQL-style EXTEND clause. + */ + private static class ExternFunction extends BaseTableFunction { - return operator; - } - - @Nullable - @Override - public DruidExpression toDruidExpression(PlannerContext plannerContext, RowSignature rowSignature, RexNode rexNode) - { - return null; - } - - private static class ExternalOperator extends SqlUserDefinedTableMacro implements AuthorizableOperator - { - public ExternalOperator(final ExternalTableMacro macro) + public ExternFunction() { - super( - new SqlIdentifier(FUNCTION_NAME, SqlParserPos.ZERO), - ReturnTypes.CURSOR, - null, - OperandTypes.sequence( - macro.signature(), - OperandTypes.family(SqlTypeFamily.STRING), - OperandTypes.family(SqlTypeFamily.STRING), - OperandTypes.family(SqlTypeFamily.STRING) - ), - macro.getParameters() - .stream() - .map(parameter -> parameter.getType(DruidTypeSystem.TYPE_FACTORY)) - .collect(Collectors.toList()), - macro - ); + super(Arrays.asList( + new Parameter(INPUT_SOURCE_PARAM, ParameterType.VARCHAR, true), + new Parameter(INPUT_FORMAT_PARAM, ParameterType.VARCHAR, true), + + // Not required: the user can either provide the signature OR + // an EXTEND clause. Checked in the implementation. + new Parameter(SIGNATURE_PARAM, ParameterType.VARCHAR, false) + )); } @Override - public Set<ResourceAction> computeResources(final SqlCall call) + public ExternalTableSpec apply( + final String fnName, + final Map<String, Object> args, + final List<ColumnSpec> columns, + final ObjectMapper jsonMapper + ) { - return Collections.singleton(EXTERNAL_RESOURCE_ACTION); + try { + final String sigValue = CatalogUtils.getString(args, SIGNATURE_PARAM); + if (sigValue == null && columns == null) { + throw new IAE( + "EXTERN requires either a %s value or an EXTEND clause", + SIGNATURE_PARAM + ); + } + if (sigValue != null && columns != null) { + throw new IAE( + "EXTERN requires either a $s value or an EXTEND clause, but not both", + SIGNATURE_PARAM + ); Review Comment: ## Unused format argument This format call refers to 0 argument(s) but supplies 1 argument(s). [Show more details](https://github.com/apache/druid/security/code-scanning/3595) ########## server/src/main/java/org/apache/druid/catalog/model/facade/DatasourceFacade.java: ########## @@ -35,37 +44,179 @@ */ public class DatasourceFacade extends TableFacade { + private static final Logger LOG = new Logger(DatasourceFacade.class); + + public static class ColumnFacade + { + public enum Kind + { + ANY, + TIME, + DIMENSION, + MEASURE + } + + private final ColumnSpec spec; + private final ParsedType type; + + public ColumnFacade(ColumnSpec spec) + { + this.spec = spec; + if (Columns.isTimeColumn(spec.name()) && spec.sqlType() == null) { + // For __time only, force a type if type is null. + this.type = TypeParser.TIME_TYPE; + } else { + this.type = TypeParser.parse(spec.sqlType()); + } + } + + public ColumnSpec spec() + { + return spec; + } + + public ParsedType type() + { + return type; + } + + public boolean hasType() + { + return type.kind() != ParsedType.Kind.ANY; + } + + public boolean isTime() + { + return type.kind() == ParsedType.Kind.TIME; + } + + public boolean isMeasure() + { + return type.kind() == ParsedType.Kind.MEASURE; + } + + public ColumnType druidType() + { + switch (type.kind()) { + case DIMENSION: + return Columns.druidType(spec.sqlType()); + case TIME: + return ColumnType.LONG; + case MEASURE: + return type.measure().storageType; + default: + return null; + } + } + + public String sqlStorageType() + { + if (isTime()) { + // Time is special: its storage type is BIGINT, but SQL requires the + // type of TIMESTAMP to allow insertion validation. + return Columns.TIMESTAMP; + } else { + return Columns.sqlType(druidType()); + } + } + + @Override + public String toString() + { + return "{spec=" + spec + ", type=" + type + "}"; Review Comment: ## Use of default toString() Default toString(): ParsedType inherits toString() from Object, and so is not suitable for printing. [Show more details](https://github.com/apache/druid/security/code-scanning/3597) ########## server/src/main/java/org/apache/druid/catalog/model/TypeParser.java: ########## @@ -0,0 +1,281 @@ +/* + * 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.druid.catalog.model; + +import org.apache.druid.catalog.model.MeasureTypes.MeasureType; +import org.apache.druid.java.util.common.IAE; +import org.apache.druid.java.util.common.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Parser for the SQL type format used in the catalog. + * <ul> + * <li>{@code null} - A null type means "use whatever Druid wants."</li> + * <li>Scalar - {@code VARCHAR}, {@code BIGINT}, etc.</li> + * <li>{@code TIMESTAMP('<grain'>} - truncated timestamp</li> + * <li>{@code fn(<type>)} - aggregate measure such as @{code COUNT}, + * {@code COUNT()} or {@code SUM(BIGINT)}.</li> + * </ul> + */ +public class TypeParser +{ + public static final ParsedType ANY_TYPE = new ParsedType(null, ParsedType.Kind.ANY, null, null); + public static final ParsedType TIME_TYPE = new ParsedType(null, ParsedType.Kind.TIME, null, null); + + public static class ParsedType + { + public enum Kind + { + ANY, + TIME, + DIMENSION, + MEASURE + } + + private final String type; + private final Kind kind; + private final String timeGrain; + private final MeasureType measureType; + + public ParsedType(String type, Kind kind, String timeGrain, MeasureType measureType) + { + this.type = type; + this.kind = kind; + this.timeGrain = timeGrain; + this.measureType = measureType; + } + + public String type() + { + return type; + } + + public Kind kind() + { + return kind; + } + + public String timeGrain() + { + return timeGrain; + } + + public MeasureType measure() + { + return measureType; + } + } + + private static class Token + { + private enum Kind + { + SYMBOL, + OPEN, + STRING, + COMMA, + CLOSE + } + + private final Kind kind; + private final String value; + + public Token(Kind kind, String value) + { + this.kind = kind; + this.value = value; + } + } + + private String input; + private int posn; + + private TypeParser(String type) + { + this.input = type; + } + + public static ParsedType parse(String type) + { + if (type == null) { + return ANY_TYPE; + } + return new TypeParser(type).parse(); + } + + private ParsedType parse() + { + Token token = parseToken(); + if (token == null) { + return ANY_TYPE; + } + if (token.kind != Token.Kind.SYMBOL) { + throw new IAE("Invalid type name"); + } + final String baseName = StringUtils.toUpperCase(token.value); + boolean isTime = Columns.isTimestamp(baseName); + token = parseToken(); + if (token == null) { + if (isTime) { + return new ParsedType(baseName, ParsedType.Kind.TIME, null, null); + } else if (Columns.isScalar(baseName)) { + return new ParsedType(baseName, ParsedType.Kind.DIMENSION, null, null); + } + return analyzeAggregate(baseName, Collections.emptyList()); + } + if (token.kind != Token.Kind.OPEN) { + throw new IAE("Invalid type name"); + } + if (!isTime && Columns.isScalar(baseName)) { + throw new IAE("Invalid type name"); + } + List<Token> args = new ArrayList<>(); + token = parseToken(); + if (token == null) { + throw new IAE("Invalid type name"); + } + if (token.kind != Token.Kind.CLOSE) { + if (token.kind != Token.Kind.SYMBOL && token.kind != Token.Kind.STRING) { + throw new IAE("Invalid type name"); + } + args.add(token); + while (true) { + token = parseToken(); + if (token == null) { + throw new IAE("Invalid type name"); + } + if (token.kind == Token.Kind.CLOSE) { + break; + } + if (token.kind != Token.Kind.COMMA) { + throw new IAE("Invalid type name"); + } + token = parseToken(); + if (token.kind != Token.Kind.SYMBOL && token.kind != Token.Kind.STRING) { + throw new IAE("Invalid type name"); + } + args.add(token); + } + } + token = parseToken(); + if (token != null) { + throw new IAE("Invalid type name"); + } + if (isTime) { + return analyzeTimestamp(baseName, args); + } else { + return analyzeAggregate(baseName, args); + } + } + + private ParsedType analyzeTimestamp(String baseName, List<Token> args) Review Comment: ## Useless parameter The parameter 'baseName' is never used. [Show more details](https://github.com/apache/druid/security/code-scanning/3591) ########## sql/src/main/java/org/apache/druid/sql/SqlPlanningException.java: ########## @@ -61,22 +60,53 @@ public SqlPlanningException(SqlParseException e) { - this(PlanningError.SQL_PARSE_ERROR, e.getMessage()); + this(e, PlanningError.SQL_PARSE_ERROR, e.getMessage()); } public SqlPlanningException(ValidationException e) { - this(PlanningError.VALIDATION_ERROR, e.getMessage()); + this(e, PlanningError.VALIDATION_ERROR, e.getMessage()); } public SqlPlanningException(CalciteContextException e) { - this(PlanningError.VALIDATION_ERROR, e.getMessage()); + this(e, PlanningError.VALIDATION_ERROR, e.getMessage()); } public SqlPlanningException(PlanningError planningError, String errorMessage) { - this(planningError.errorCode, errorMessage, planningError.errorClass); + this( + null, + planningError.errorCode, + errorMessage, + planningError.errorClass + ); + } + + /** + * Constructor which preserves the source exception. Essential during debugging + * to see what really failed. If we don't want to show the user the underlying + * error, then don't serialize it out to the user. We must still preserve the + * cause internally to save time and sanity for developers. + */ + public SqlPlanningException(Exception e, PlanningError planningError, String errorMessage) + { + this( + e, + planningError.errorCode, + errorMessage, + planningError.errorClass + ); + } + + private SqlPlanningException( + Exception e, Review Comment: ## Useless parameter The parameter 'e' is never used. [Show more details](https://github.com/apache/druid/security/code-scanning/3592) ########## server/src/main/java/org/apache/druid/catalog/model/table/ResolvedExternalTable.java: ########## @@ -0,0 +1,149 @@ +/* + * 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.druid.catalog.model.table; + +import org.apache.druid.catalog.model.CatalogUtils; +import org.apache.druid.catalog.model.ResolvedTable; +import org.apache.druid.catalog.model.TableDefnRegistry; +import org.apache.druid.data.input.InputFormat; +import org.apache.druid.data.input.InputSource; +import org.apache.druid.java.util.common.IAE; + +import java.util.HashMap; +import java.util.Map; + +/** + * Internal class to hold the intermediate form of an external table: the + * input source and input format properties converted to Java maps, and the + * types of each resolved to the corresponding definitions. Used to validate + * a table specification, and to convert a table specification to an + * {@link ExternalTableSpec} when used in SQL. + */ +public class ResolvedExternalTable +{ + private final ResolvedTable table; + private Map<String, Object> inputSourceMap; + private Map<String, Object> inputFormatMap; + private InputSourceDefn inputSourceDefn; + private InputFormatDefn inputFormatDefn; + + /** + * Construct a resolved external table by extracting the input source + * and input format properties, and converting each to a Java map. + * Validates that the input source is present: the format is optional. + * <p> + * Note: does <i>not</i> resolve the input source and input format + * definitions: that is done as a separate step when needed. + * + * @see {@link #resolve(TableDefnRegistry)}. + */ + public ResolvedExternalTable(final ResolvedTable table) + { + this.table = table; + Map<String, Object> map = table.mapProperty(ExternalTableDefn.SOURCE_PROPERTY); + if (map == null || map.isEmpty()) { + throw new IAE("%s property is required", ExternalTableDefn.SOURCE_PROPERTY); + } + this.inputSourceMap = new HashMap<>(map); + if (this.inputSourceMap == null || this.inputSourceMap.isEmpty()) { Review Comment: ## Useless null check This check is useless. [this.inputSourceMap](1) cannot be null at this check, since [new HashMap<String,Object>(...)](2) always is non-null. [Show more details](https://github.com/apache/druid/security/code-scanning/3593) ########## server/src/test/java/org/apache/druid/catalog/model/table/InlineInputSourceDefnTest.java: ########## @@ -0,0 +1,218 @@ +/* + * 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.druid.catalog.model.table; + +import com.google.common.collect.ImmutableMap; +import org.apache.druid.catalog.model.ColumnSpec; +import org.apache.druid.catalog.model.Columns; +import org.apache.druid.catalog.model.ResolvedTable; +import org.apache.druid.catalog.model.TableMetadata; +import org.apache.druid.catalog.model.table.InputFormats.CsvFormatDefn; +import org.apache.druid.catalog.model.table.InputFormats.FlatTextFormatDefn; +import org.apache.druid.data.input.impl.CsvInputFormat; +import org.apache.druid.data.input.impl.InlineInputSource; +import org.apache.druid.java.util.common.IAE; +import org.junit.Test; + +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class InlineInputSourceDefnTest extends BaseExternTableTest +{ + @Test + public void testValidateEmptyInputSource() + { + // No data property: not valid + TableMetadata table = TableBuilder.external("foo") + .inputSource(ImmutableMap.of("type", InlineInputSource.TYPE_KEY)) + .inputFormat(CSV_FORMAT) + .column("x", Columns.VARCHAR) + .build(); + ResolvedTable resolved = registry.resolve(table.spec()); + assertThrows(IAE.class, () -> resolved.validate()); + } + + @Test + public void testValidateNoFormat() + { + // No format: not valid. For inline, format must be provided to match data + TableMetadata table = TableBuilder.external("foo") + .inputSource(toMap(new InlineInputSource("a\n"))) + .column("x", Columns.VARCHAR) + .build(); + ResolvedTable resolved = registry.resolve(table.spec()); + assertThrows(IAE.class, () -> resolved.validate()); + } + + @Test + public void testValidateNoColumns() throws URISyntaxException + { + TableMetadata table = TableBuilder.external("foo") + .inputSource(toMap(new InlineInputSource("a\n"))) + .inputFormat(CSV_FORMAT) + .build(); + ResolvedTable resolved = registry.resolve(table.spec()); + assertThrows(IAE.class, () -> resolved.validate()); + } + + @Test + public void testValidateGood() + { + TableMetadata table = TableBuilder.external("foo") + .inputSource(toMap(new InlineInputSource("a\n"))) + .inputFormat(CSV_FORMAT) + .column("x", Columns.VARCHAR) + .build(); + ResolvedTable resolved = registry.resolve(table.spec()); + resolved.validate(); + } + + @Test + public void testFullTableFnBasics() + { + InputSourceDefn defn = registry.inputSourceDefnFor(InlineInputSourceDefn.TYPE_KEY); + TableFunction fn = defn.adHocTableFn(); + assertNotNull(fn); + assertTrue(hasParam(fn, InlineInputSourceDefn.DATA_PROPERTY)); + assertTrue(hasParam(fn, FormattedInputSourceDefn.FORMAT_PARAMETER)); + assertTrue(hasParam(fn, FlatTextFormatDefn.LIST_DELIMITER_PARAMETER)); + } + + @Test + public void testMissingArgs() + { + InputSourceDefn defn = registry.inputSourceDefnFor(InlineInputSourceDefn.TYPE_KEY); + TableFunction fn = defn.adHocTableFn(); + assertThrows(IAE.class, () -> fn.apply("x", new HashMap<>(), Collections.emptyList(), mapper)); + } + + @Test + public void testMissingFormat() + { + InputSourceDefn defn = registry.inputSourceDefnFor(InlineInputSourceDefn.TYPE_KEY); + TableFunction fn = defn.adHocTableFn(); + Map<String, Object> args = new HashMap<>(); Review Comment: ## Container contents are never accessed The contents of this container are never accessed. [Show more details](https://github.com/apache/druid/security/code-scanning/3594) ########## server/src/main/java/org/apache/druid/catalog/model/table/ResolvedExternalTable.java: ########## @@ -0,0 +1,149 @@ +/* + * 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.druid.catalog.model.table; + +import org.apache.druid.catalog.model.CatalogUtils; +import org.apache.druid.catalog.model.ResolvedTable; +import org.apache.druid.catalog.model.TableDefnRegistry; +import org.apache.druid.data.input.InputFormat; +import org.apache.druid.data.input.InputSource; +import org.apache.druid.java.util.common.IAE; + +import java.util.HashMap; +import java.util.Map; + +/** + * Internal class to hold the intermediate form of an external table: the + * input source and input format properties converted to Java maps, and the + * types of each resolved to the corresponding definitions. Used to validate + * a table specification, and to convert a table specification to an + * {@link ExternalTableSpec} when used in SQL. + */ +public class ResolvedExternalTable +{ + private final ResolvedTable table; + private Map<String, Object> inputSourceMap; + private Map<String, Object> inputFormatMap; + private InputSourceDefn inputSourceDefn; + private InputFormatDefn inputFormatDefn; + + /** + * Construct a resolved external table by extracting the input source + * and input format properties, and converting each to a Java map. + * Validates that the input source is present: the format is optional. + * <p> + * Note: does <i>not</i> resolve the input source and input format + * definitions: that is done as a separate step when needed. + * + * @see {@link #resolve(TableDefnRegistry)}. + */ + public ResolvedExternalTable(final ResolvedTable table) + { + this.table = table; + Map<String, Object> map = table.mapProperty(ExternalTableDefn.SOURCE_PROPERTY); + if (map == null || map.isEmpty()) { + throw new IAE("%s property is required", ExternalTableDefn.SOURCE_PROPERTY); + } + this.inputSourceMap = new HashMap<>(map); + if (this.inputSourceMap == null || this.inputSourceMap.isEmpty()) { + throw new IAE("%s property is required", ExternalTableDefn.SOURCE_PROPERTY); + } + map = table.mapProperty(ExternalTableDefn.FORMAT_PROPERTY); + this.inputFormatMap = map == null ? null : new HashMap<>(map); + } + + public ResolvedTable resolvedTable() + { + return table; + } + + public Map<String, Object> formatMap() + { + return inputFormatMap; + } + + public Map<String, Object> sourceMap() Review Comment: ## Exposing internal representation sourceMap exposes the internal representation stored in field inputSourceMap. The value may be modified [after this call to sourceMap](1). sourceMap exposes the internal representation stored in field inputSourceMap. The value may be modified [after this call to sourceMap](2). [Show more details](https://github.com/apache/druid/security/code-scanning/3596) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
