diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java index 7d9a55d904b1..12b717ec002f 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.java.util.common.granularity.Granularities; import org.apache.druid.java.util.common.guava.Sequence; @@ -46,6 +45,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; import org.apache.druid.sql.calcite.planner.PlannerResult; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import org.apache.druid.timeline.DataSegment; @@ -421,7 +421,7 @@ public void setup() ); closer.register(walker); - final SchemaPlus rootSchema = + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); plannerFactory = new PlannerFactory( rootSchema, diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java index cb5ce5f45d6d..3f99b241f7dc 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.java.util.common.granularity.Granularities; import org.apache.druid.java.util.common.guava.Sequence; @@ -42,6 +41,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; import org.apache.druid.sql.calcite.planner.PlannerResult; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import org.apache.druid.timeline.DataSegment; @@ -264,7 +264,7 @@ public void setup() ); closer.register(walker); - final SchemaPlus rootSchema = + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); plannerFactory = new PlannerFactory( rootSchema, diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java index dda14ff0b4b8..9e805d8daf00 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java @@ -19,7 +19,6 @@ package org.apache.druid.benchmark.query; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.java.util.common.granularity.Granularities; @@ -43,6 +42,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; import org.apache.druid.sql.calcite.planner.PlannerResult; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import org.apache.druid.timeline.DataSegment; @@ -111,7 +111,7 @@ public void setup() final PlannerConfig plannerConfig = new PlannerConfig(); this.walker = closer.register(new SpecificSegmentsQuerySegmentWalker(conglomerate).add(dataSegment, index)); - final SchemaPlus rootSchema = + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); plannerFactory = new PlannerFactory( rootSchema, diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/BasicRoleBasedAuthorizer.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/BasicRoleBasedAuthorizer.java index f307b9ea029e..206e89544675 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/BasicRoleBasedAuthorizer.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/BasicRoleBasedAuthorizer.java @@ -36,6 +36,7 @@ import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -116,7 +117,7 @@ private boolean permissionCheck(Resource resource, Action action, BasicAuthorize } Resource permissionResource = permission.getResourceAction().getResource(); - if (permissionResource.getType() != resource.getType()) { + if (!Objects.equals(permissionResource.getType(), resource.getType())) { return false; } diff --git a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/BasicAuthUtilsTest.java b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/BasicAuthUtilsTest.java index 7bcb1e24c778..60235fbf84bc 100644 --- a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/BasicAuthUtilsTest.java +++ b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/BasicAuthUtilsTest.java @@ -60,6 +60,7 @@ public void testPermissionSerdeIsChillAboutUnknownEnumStuffs() throws JsonProces final String thirdRoleName = "third-role"; final ResourceAction fooRead = new ResourceAction(new Resource("foo", ResourceType.DATASOURCE), Action.READ); final ResourceAction barRead = new ResourceAction(new Resource("bar", ResourceType.DATASOURCE), Action.READ); + final ResourceAction customRead = new ResourceAction(new Resource("bar", "CUSTOM"), Action.READ); final ObjectMapper mapper = TestHelper.makeJsonMapper(); mapper.registerModules(new BasicSecurityDruidModule().getJacksonModules()); @@ -76,7 +77,7 @@ public void testPermissionSerdeIsChillAboutUnknownEnumStuffs() throws JsonProces ) ) ); - // bad ResourceType + // custom ResourceType rawMap.put( otherRoleName, ImmutableMap.of( @@ -89,12 +90,7 @@ public void testPermissionSerdeIsChillAboutUnknownEnumStuffs() throws JsonProces "resourceNamePattern", "foo" ), ImmutableMap.of( - "resourceAction", - ImmutableMap.of( - "resource", - ImmutableMap.of("name", "bar", "type", "UNKNOWN"), - "action", "READ" - ), + "resourceAction", customRead, "resourceNamePattern", "bar" ) ) @@ -141,11 +137,11 @@ public void testPermissionSerdeIsChillAboutUnknownEnumStuffs() throws JsonProces roleMap.get(someRoleName).getPermissions() ); - // this one has an unknown ResourceType, expect only 1 permission to deserialize correctly and failure ignored + // this one has custom resource type... this test is somewhat pointless, it made more sense when type was an enum Assert.assertTrue(roleMap.containsKey(otherRoleName)); - Assert.assertEquals(1, roleMap.get(otherRoleName).getPermissions().size()); + Assert.assertEquals(2, roleMap.get(otherRoleName).getPermissions().size()); Assert.assertEquals( - BasicAuthorizerPermission.makePermissionList(ImmutableList.of(fooRead)), + BasicAuthorizerPermission.makePermissionList(ImmutableList.of(fooRead, customRead)), roleMap.get(otherRoleName).getPermissions() ); diff --git a/extensions-core/druid-ranger-security/src/main/java/org/apache/druid/security/ranger/authorizer/RangerAuthorizer.java b/extensions-core/druid-ranger-security/src/main/java/org/apache/druid/security/ranger/authorizer/RangerAuthorizer.java index 330a7c0250d5..1d4bf1578b26 100644 --- a/extensions-core/druid-ranger-security/src/main/java/org/apache/druid/security/ranger/authorizer/RangerAuthorizer.java +++ b/extensions-core/druid-ranger-security/src/main/java/org/apache/druid/security/ranger/authorizer/RangerAuthorizer.java @@ -126,7 +126,7 @@ class RangerDruidResource extends RangerAccessResourceImpl { public RangerDruidResource(Resource resource) { - setValue(resource.getType().name().toLowerCase(Locale.ENGLISH), resource.getName()); + setValue(resource.getType().toLowerCase(Locale.ENGLISH), resource.getName()); } } diff --git a/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java b/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java index 6ba34f6794f4..2b4449826476 100644 --- a/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java +++ b/server/src/main/java/org/apache/druid/server/security/AuthorizationUtils.java @@ -25,6 +25,7 @@ import org.apache.druid.java.util.common.ISE; import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -359,49 +360,27 @@ public static Map> filterAuthorizedRes return filteredResources; } + /** + * This method constructs a 'superuser' set of permissions composed of {@link Action#READ} and {@link Action#WRITE} + * permissions for all known {@link ResourceType#knownTypes()} for any {@link Authorizer} implementation which is + * built on pattern matching with a regex. + * + * Note that if any {@link Resource} exist that use custom types not registered with + * {@link ResourceType#registerResourceType}, those permissions will not be included in this list and will need to + * be added manually. + */ public static List makeSuperUserPermissions() { - ResourceAction datasourceR = new ResourceAction( - new Resource(".*", ResourceType.DATASOURCE), - Action.READ - ); - - ResourceAction datasourceW = new ResourceAction( - new Resource(".*", ResourceType.DATASOURCE), - Action.WRITE - ); - - ResourceAction viewR = new ResourceAction( - new Resource(".*", ResourceType.VIEW), - Action.READ - ); - - ResourceAction viewW = new ResourceAction( - new Resource(".*", ResourceType.VIEW), - Action.WRITE - ); - - ResourceAction configR = new ResourceAction( - new Resource(".*", ResourceType.CONFIG), - Action.READ - ); - - ResourceAction configW = new ResourceAction( - new Resource(".*", ResourceType.CONFIG), - Action.WRITE - ); - - ResourceAction stateR = new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ); - - ResourceAction stateW = new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.WRITE - ); - - return Lists.newArrayList(datasourceR, datasourceW, viewR, viewW, configR, configW, stateR, stateW); + List allReadAndWrite = new ArrayList<>(ResourceType.knownTypes().size() * 2); + for (String type : ResourceType.knownTypes()) { + allReadAndWrite.add( + new ResourceAction(new Resource(".*", type), Action.READ) + ); + allReadAndWrite.add( + new ResourceAction(new Resource(".*", type), Action.WRITE) + ); + } + return allReadAndWrite; } /** diff --git a/server/src/main/java/org/apache/druid/server/security/Resource.java b/server/src/main/java/org/apache/druid/server/security/Resource.java index 6770bdaf5cbc..710673f52b4d 100644 --- a/server/src/main/java/org/apache/druid/server/security/Resource.java +++ b/server/src/main/java/org/apache/druid/server/security/Resource.java @@ -22,17 +22,19 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + public class Resource { public static final Resource STATE_RESOURCE = new Resource("STATE", ResourceType.STATE); private final String name; - private final ResourceType type; + private final String type; @JsonCreator public Resource( @JsonProperty("name") String name, - @JsonProperty("type") ResourceType type + @JsonProperty("type") String type ) { this.name = name; @@ -46,7 +48,7 @@ public String getName() } @JsonProperty - public ResourceType getType() + public String getType() { return type; } @@ -66,16 +68,14 @@ public boolean equals(Object o) if (!name.equals(resource.name)) { return false; } - return type == resource.type; + return Objects.equals(type, resource.type); } @Override public int hashCode() { - int result = name.hashCode(); - result = 31 * result + type.hashCode(); - return result; + return Objects.hash(name, type); } @Override diff --git a/server/src/main/java/org/apache/druid/server/security/ResourceType.java b/server/src/main/java/org/apache/druid/server/security/ResourceType.java index 04cf43af184b..0e02572b6151 100644 --- a/server/src/main/java/org/apache/druid/server/security/ResourceType.java +++ b/server/src/main/java/org/apache/druid/server/security/ResourceType.java @@ -19,22 +19,44 @@ package org.apache.druid.server.security; -import com.fasterxml.jackson.annotation.JsonCreator; -import org.apache.druid.java.util.common.StringUtils; +import com.google.common.collect.Sets; -public enum ResourceType +import java.util.Set; + +/** + * Set of built-in and 'registered' {@link Resource} types for use by {@link Authorizer} + */ +public class ResourceType { - DATASOURCE, - VIEW, - CONFIG, - STATE; + public static final String DATASOURCE = "DATASOURCE"; + public static final String VIEW = "VIEW"; + public static final String CONFIG = "CONFIG"; + public static final String STATE = "STATE"; + + private static final Set KNOWN_TYPES = Sets.newConcurrentHashSet(); + + // initialize built-ins + static { + registerResourceType(DATASOURCE); + registerResourceType(VIEW); + registerResourceType(CONFIG); + registerResourceType(STATE); + } + + /** + * Set of 'known' {@link Resource} types which have been registered with {@link #registerResourceType}, for use by + * utility methods looking to construct permission sets for all types (e.g. 'superuser' permission set) + */ + public static Set knownTypes() + { + return KNOWN_TYPES; + } - @JsonCreator - public static ResourceType fromString(String name) + /** + * 'register' a 'known' type of {@link Resource} to make available via {@link #knownTypes()} + */ + public static void registerResourceType(String type) { - if (name == null) { - return null; - } - return valueOf(StringUtils.toUpperCase(name)); + KNOWN_TYPES.add(type); } } diff --git a/server/src/test/java/org/apache/druid/server/security/AuthorizationUtilsTest.java b/server/src/test/java/org/apache/druid/server/security/AuthorizationUtilsTest.java index 8c6ec8169e80..a9d87a4fd79f 100644 --- a/server/src/test/java/org/apache/druid/server/security/AuthorizationUtilsTest.java +++ b/server/src/test/java/org/apache/druid/server/security/AuthorizationUtilsTest.java @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; public class AuthorizationUtilsTest { @@ -88,16 +89,26 @@ public Iterable apply(@Nullable String input) @Test public void testMakeSuperuserPermissions() { + final String customType = "CUSTOM"; + ResourceType.registerResourceType(customType); final List permissions = AuthorizationUtils.makeSuperUserPermissions(); // every type and action should have a wildcard pattern - for (ResourceType type : ResourceType.values()) { + for (String type : ResourceType.knownTypes()) { for (Action action : Action.values()) { Assert.assertTrue( permissions.stream() - .filter(ra -> type == ra.getResource().getType()) + .filter(ra -> Objects.equals(type, ra.getResource().getType())) .anyMatch(ra -> action == ra.getAction() && ".*".equals(ra.getResource().getName())) ); } } + // custom type should be there too + for (Action action : Action.values()) { + Assert.assertTrue( + permissions.stream() + .filter(ra -> Objects.equals(customType, ra.getResource().getType())) + .anyMatch(ra -> action == ra.getAction() && ".*".equals(ra.getResource().getName())) + ); + } } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidPlanner.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidPlanner.java index 1618e6107907..a1b9149be6ac 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidPlanner.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidPlanner.java @@ -120,7 +120,7 @@ public ValidationResult validate(final String sql) throws SqlParseException, Val throw new ValidationException(e); } SqlResourceCollectorShuttle resourceCollectorShuttle = - new SqlResourceCollectorShuttle(validator, frameworkConfig.getDefaultSchema().getName()); + new SqlResourceCollectorShuttle(validator, plannerContext); validated.accept(resourceCollectorShuttle); plannerContext.setResources(resourceCollectorShuttle.getResources()); return new ValidationResult(resourceCollectorShuttle.getResources()); diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java index 59d4bd8f0194..0ec442e7c6b9 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java @@ -33,10 +33,12 @@ import org.apache.druid.server.security.Access; import org.apache.druid.server.security.AuthenticationResult; import org.apache.druid.server.security.Resource; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; +import javax.annotation.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -68,6 +70,7 @@ public class PlannerContext private final ExprMacroTable macroTable; private final PlannerConfig plannerConfig; private final DateTime localNow; + private final DruidSchemaCatalog rootSchema; private final Map queryContext; private final String sqlQueryId; private final boolean stringifyArrays; @@ -87,12 +90,14 @@ private PlannerContext( final PlannerConfig plannerConfig, final DateTime localNow, final boolean stringifyArrays, + final DruidSchemaCatalog rootSchema, final Map queryContext ) { this.operatorTable = operatorTable; this.macroTable = macroTable; this.plannerConfig = Preconditions.checkNotNull(plannerConfig, "plannerConfig"); + this.rootSchema = rootSchema; this.queryContext = queryContext != null ? new HashMap<>(queryContext) : new HashMap<>(); this.localNow = Preconditions.checkNotNull(localNow, "localNow"); this.stringifyArrays = stringifyArrays; @@ -109,6 +114,7 @@ public static PlannerContext create( final DruidOperatorTable operatorTable, final ExprMacroTable macroTable, final PlannerConfig plannerConfig, + final DruidSchemaCatalog rootSchema, final Map queryContext ) { @@ -150,6 +156,7 @@ public static PlannerContext create( plannerConfig.withOverrides(queryContext), utcNow.withZone(timeZone), stringifyArrays, + rootSchema, queryContext ); } @@ -179,6 +186,12 @@ public DateTimeZone getTimeZone() return localNow.getZone(); } + @Nullable + public String getSchemaResourceType(String schema, String resourceName) + { + return rootSchema.getResourceType(schema, resourceName); + } + public Map getQueryContext() { return queryContext; diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java index 9f86eda98bfd..a96818e2e856 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java @@ -29,7 +29,6 @@ import org.apache.calcite.plan.Context; import org.apache.calcite.plan.ConventionTraitDef; import org.apache.calcite.rel.RelCollationTraitDef; -import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.sql.parser.SqlParser; import org.apache.calcite.sql.validate.SqlConformance; @@ -44,6 +43,7 @@ import org.apache.druid.server.security.AuthorizerMapper; import org.apache.druid.server.security.NoopEscalator; import org.apache.druid.sql.calcite.rel.QueryMaker; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.schema.DruidSchemaName; import java.util.Map; @@ -60,7 +60,7 @@ public class PlannerFactory .setConformance(DruidConformance.instance()) .build(); - private final SchemaPlus rootSchema; + private final DruidSchemaCatalog rootSchema; private final QueryLifecycleFactory queryLifecycleFactory; private final DruidOperatorTable operatorTable; private final ExprMacroTable macroTable; @@ -71,7 +71,7 @@ public class PlannerFactory @Inject public PlannerFactory( - final SchemaPlus rootSchema, + final DruidSchemaCatalog rootSchema, final QueryLifecycleFactory queryLifecycleFactory, final DruidOperatorTable operatorTable, final ExprMacroTable macroTable, @@ -100,6 +100,7 @@ public DruidPlanner createPlanner(final Map queryContext) operatorTable, macroTable, plannerConfig, + rootSchema, queryContext ); final QueryMaker queryMaker = new QueryMaker(queryLifecycleFactory, plannerContext, jsonMapper); diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/SqlResourceCollectorShuttle.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/SqlResourceCollectorShuttle.java index aba6689aaf42..a333b0342da1 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/SqlResourceCollectorShuttle.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/SqlResourceCollectorShuttle.java @@ -28,7 +28,6 @@ import org.apache.calcite.sql.validate.SqlValidatorTable; import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.ResourceType; -import org.apache.druid.sql.calcite.schema.NamedViewSchema; import java.util.HashSet; import java.util.List; @@ -45,14 +44,14 @@ public class SqlResourceCollectorShuttle extends SqlShuttle { private final Set resources; + private final PlannerContext plannerContext; private final SqlValidator validator; - private final String druidSchemaName; - public SqlResourceCollectorShuttle(SqlValidator validator, String druidSchemaName) + public SqlResourceCollectorShuttle(SqlValidator validator, PlannerContext plannerContext) { this.validator = validator; this.resources = new HashSet<>(); - this.druidSchemaName = druidSchemaName; + this.plannerContext = plannerContext; } @Override @@ -69,10 +68,10 @@ public SqlNode visit(SqlIdentifier id) // 'schema'.'identifier' if (qualifiedNameParts.size() == 2) { final String schema = qualifiedNameParts.get(0); - if (druidSchemaName.equals(schema)) { - resources.add(new Resource(qualifiedNameParts.get(1), ResourceType.DATASOURCE)); - } else if (NamedViewSchema.NAME.equals(schema)) { - resources.add(new Resource(qualifiedNameParts.get(1), ResourceType.VIEW)); + final String resourceName = qualifiedNameParts.get(1); + final String resourceType = plannerContext.getSchemaResourceType(schema, resourceName); + if (resourceType != null) { + resources.add(new Resource(resourceName, resourceType)); } } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModule.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModule.java index 43e8ad7d8910..0a8de2922713 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModule.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModule.java @@ -26,7 +26,6 @@ import com.google.inject.Singleton; import com.google.inject.name.Named; import com.google.inject.name.Names; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.guice.LifecycleModule; import org.apache.druid.sql.guice.SqlBindings; @@ -45,7 +44,7 @@ public void configure(Binder binder) binder.bind(String.class).annotatedWith(DruidSchemaName.class).toInstance(DRUID_SCHEMA_NAME); // Should only be used by the information schema - binder.bind(SchemaPlus.class) + binder.bind(DruidSchemaCatalog.class) .annotatedWith(Names.named(INCOMPLETE_SCHEMA)) .toProvider(RootSchemaProvider.class) .in(Scopes.SINGLETON); @@ -65,9 +64,9 @@ public void configure(Binder binder) @Provides @Singleton - private SchemaPlus getRootSchema(@Named(INCOMPLETE_SCHEMA) SchemaPlus rootSchema, InformationSchema informationSchema) + private DruidSchemaCatalog getRootSchema(@Named(INCOMPLETE_SCHEMA) DruidSchemaCatalog rootSchema, InformationSchema informationSchema) { - rootSchema.add(INFORMATION_SCHEMA_NAME, informationSchema); + rootSchema.getRootSchema().add(INFORMATION_SCHEMA_NAME, informationSchema); return rootSchema; } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidSchemaCatalog.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidSchemaCatalog.java new file mode 100644 index 000000000000..34f15af4baf0 --- /dev/null +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/DruidSchemaCatalog.java @@ -0,0 +1,138 @@ +/* + * 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.sql.calcite.schema; + +import org.apache.calcite.schema.SchemaPlus; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * The Druid 'catalog', containing information about all Druid schemas which are available. This packages both the 'root + * level' Calcite {@link SchemaPlus} and a map of the {@link NamedSchema} which were used to populate it, keyed by + * {@link NamedSchema#getSchemaName()}. + * + * The {@link #rootSchema} is a top level Calcite schema, which contains all {@link NamedSchema#getSchema()} and + * {@link InformationSchema} added as sub-schemas, and this class provides convenience methods to do things like + * fetch a specific {@link SchemaPlus} by name or list the set of all schemas available, and is used during query + * planning and execution. + * + * {@link #namedSchemas} contains all {@link NamedSchema}, which should be everything except {@link InformationSchema}. + * These are used primarily for {@link #getResourceType(String, String)}, which given the name of a table or function + * that belongs to some {@link NamedSchema}, lookup the most appropriate value to use for + * {@link org.apache.druid.server.security.Resource#getType()} to use for authorization. + */ +public class DruidSchemaCatalog +{ + private final SchemaPlus rootSchema; + private final Map namedSchemas; + + public DruidSchemaCatalog( + SchemaPlus rootSchema, + Map schemas + ) + { + this.rootSchema = rootSchema; + this.namedSchemas = schemas; + } + + /** + * Root calcite schema, used to plan and execute queries + */ + public SchemaPlus getRootSchema() + { + return rootSchema; + } + + /** + * Get all {@link NamedSchema} which belong to the Druid catalog + */ + public Map getNamedSchemas() + { + return namedSchemas; + } + + /** + * Get a {@link NamedSchema} by {@link NamedSchema#getSchemaName()} + */ + public NamedSchema getNamedSchema(String schemaName) + { + return namedSchemas.get(schemaName); + } + + /** + * Get a specific {@link SchemaPlus} by {@link NamedSchema#getSchemaName()} + */ + public SchemaPlus getSubSchema(String name) + { + return rootSchema.getSubSchema(name); + } + + /** + * Get all sub-schemas defined on {@link #rootSchema} + */ + public Set getSubSchemaNames() + { + return rootSchema.getSubSchemaNames(); + } + + /** + * Given the name of a {@link NamedSchema} and the name of a table or function that belongs to that schema, return + * the appropriate value to use for {@link org.apache.druid.server.security.Resource#getType()} during authorization + */ + @Nullable + public String getResourceType(String schema, String resourceName) + { + if (namedSchemas.containsKey(schema)) { + return namedSchemas.get(schema).getSchemaResourceType(resourceName); + } + return null; + } + + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DruidSchemaCatalog that = (DruidSchemaCatalog) o; + return rootSchema.equals(that.rootSchema) && namedSchemas.equals(that.namedSchemas); + } + + @Override + public int hashCode() + { + return Objects.hash(rootSchema, namedSchemas); + } + + @Override + public String toString() + { + return "DruidSchemaCatalog{" + + "schemas=" + getSubSchemaNames() + + '}'; + } +} diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/InformationSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/InformationSchema.java index 9aa411bb8263..96fe42c07160 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/InformationSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/InformationSchema.java @@ -23,7 +23,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -49,9 +48,11 @@ import org.apache.druid.java.util.emitter.EmittingLogger; import org.apache.druid.segment.column.RowSignature; import org.apache.druid.segment.column.ValueType; +import org.apache.druid.server.security.Action; import org.apache.druid.server.security.AuthenticationResult; import org.apache.druid.server.security.AuthorizationUtils; import org.apache.druid.server.security.AuthorizerMapper; +import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.ResourceAction; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.table.DruidTable; @@ -111,26 +112,18 @@ public class InformationSchema extends AbstractSchema .add("JDBC_TYPE", ValueType.LONG) .build(); private static final RelDataTypeSystem TYPE_SYSTEM = RelDataTypeSystem.DEFAULT; - private static final Function> DRUID_TABLE_RA_GENERATOR = datasourceName -> { - return Collections.singletonList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(datasourceName)); - }; - private static final Function> VIEW_TABLE_RA_GENERATOR = viewName -> { - return Collections.singletonList(AuthorizationUtils.VIEW_READ_RA_GENERATOR.apply(viewName)); - }; private static final String INFO_TRUE = "YES"; private static final String INFO_FALSE = "NO"; - private final SchemaPlus rootSchema; + private final DruidSchemaCatalog rootSchema; private final Map tableMap; private final AuthorizerMapper authorizerMapper; - private final String druidSchemaName; @Inject public InformationSchema( - @Named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA) final SchemaPlus rootSchema, - final AuthorizerMapper authorizerMapper, - @DruidSchemaName String druidSchemaName + @Named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA) final DruidSchemaCatalog rootSchema, + final AuthorizerMapper authorizerMapper ) { this.rootSchema = Preconditions.checkNotNull(rootSchema, "rootSchema"); @@ -140,7 +133,6 @@ TABLES_TABLE, new TablesTable(), COLUMNS_TABLE, new ColumnsTable() ); this.authorizerMapper = authorizerMapper; - this.druidSchemaName = druidSchemaName; } @Override @@ -364,7 +356,7 @@ public Iterable apply(final String functionName) return generateColumnMetadata( schemaName, functionName, - viewMacro.apply(ImmutableList.of()), + viewMacro.apply(Collections.emptyList()), typeFactory ); } @@ -483,20 +475,11 @@ private Set getAuthorizedTableNamesFromSubSchema( final AuthenticationResult authenticationResult ) { - if (druidSchemaName.equals(subSchema.getName())) { - // The "druid" schema's tables represent Druid datasources which require authorization - return ImmutableSet.copyOf( - AuthorizationUtils.filterAuthorizedResources( - authenticationResult, - subSchema.getTableNames(), - DRUID_TABLE_RA_GENERATOR, - authorizerMapper - ) - ); - } else { - // for non "druid" schema, we don't filter anything - return subSchema.getTableNames(); - } + return getAuthorizedNamesFromNamedSchema( + authenticationResult, + rootSchema.getNamedSchema(subSchema.getName()), + subSchema.getTableNames() + ); } private Set getAuthorizedFunctionNamesFromSubSchema( @@ -504,19 +487,40 @@ private Set getAuthorizedFunctionNamesFromSubSchema( final AuthenticationResult authenticationResult ) { - if (NamedViewSchema.NAME.equals(subSchema.getName())) { - // The "view" subschema functions represent views on Druid datasources + return getAuthorizedNamesFromNamedSchema( + authenticationResult, + rootSchema.getNamedSchema(subSchema.getName()), + subSchema.getFunctionNames() + ); + } + + private Set getAuthorizedNamesFromNamedSchema( + final AuthenticationResult authenticationResult, + final NamedSchema schema, + final Set names + ) + { + if (schema != null) { return ImmutableSet.copyOf( AuthorizationUtils.filterAuthorizedResources( authenticationResult, - subSchema.getFunctionNames(), - VIEW_TABLE_RA_GENERATOR, + names, + name -> { + final String resoureType = schema.getSchemaResourceType(name); + if (resoureType != null) { + return Collections.singletonList( + new ResourceAction(new Resource(name, resoureType), Action.READ) + ); + } else { + return Collections.emptyList(); + } + }, authorizerMapper ) ); } else { - // for non "druid" schema, we don't filter anything - return subSchema.getFunctionNames(); + // for schemas with no resource type, or that are not named schemas, we don't filter anything + return names; } } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedDruidSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedDruidSchema.java index a48ccf23d648..2e8de70c2feb 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedDruidSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedDruidSchema.java @@ -21,17 +21,18 @@ import com.google.inject.Inject; import org.apache.calcite.schema.Schema; +import org.apache.druid.server.security.ResourceType; /** * The schema for Druid tables to be accessible via SQL. */ -class NamedDruidSchema implements NamedSchema +public class NamedDruidSchema implements NamedSchema { private final DruidSchema druidSchema; private final String druidSchemaName; @Inject - NamedDruidSchema(DruidSchema druidSchema, @DruidSchemaName String druidSchemaName) + public NamedDruidSchema(DruidSchema druidSchema, @DruidSchemaName String druidSchemaName) { this.druidSchema = druidSchema; this.druidSchemaName = druidSchemaName; @@ -43,6 +44,12 @@ public String getSchemaName() return druidSchemaName; } + @Override + public String getSchemaResourceType(String resourceName) + { + return ResourceType.DATASOURCE; + } + @Override public Schema getSchema() { diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedLookupSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedLookupSchema.java index a1448661be2a..eb91949126b7 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedLookupSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedLookupSchema.java @@ -27,12 +27,12 @@ */ public class NamedLookupSchema implements NamedSchema { - private static final String NAME = "lookup"; + public static final String NAME = "lookup"; private final LookupSchema lookupSchema; @Inject - NamedLookupSchema(LookupSchema lookupSchema) + public NamedLookupSchema(LookupSchema lookupSchema) { this.lookupSchema = lookupSchema; } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSchema.java index c4ba8271eb80..da951aa62733 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSchema.java @@ -21,6 +21,8 @@ import org.apache.calcite.schema.Schema; +import javax.annotation.Nullable; + /** * This interface provides everything that is needed to register a {@link Schema} as a sub schema to the root schema * of Druid SQL. The {@link #getSchemaName()} will be used to access the provided {@link Schema} via SQL. @@ -32,6 +34,17 @@ public interface NamedSchema */ String getSchemaName(); + /** + * For a given name of a table, function, etc of this schema, return the value most appropriate to use for + * {@link org.apache.druid.server.security.Resource#getType()} for the resource, used during authorization. If this + * method returns null then the resource does not need any authorization. + */ + @Nullable + default String getSchemaResourceType(String resourceName) + { + return null; + } + /** * @return The Schema that Calcite should use. */ diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java index 1bb02233def1..3d42f47e0673 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java @@ -25,14 +25,14 @@ /** * The schema for Druid system tables to be accessible via SQL. */ -class NamedSystemSchema implements NamedSchema +public class NamedSystemSchema implements NamedSchema { - private static final String NAME = "sys"; + public static final String NAME = "sys"; private final SystemSchema systemSchema; @Inject - NamedSystemSchema(SystemSchema systemSchema) + public NamedSystemSchema(SystemSchema systemSchema) { this.systemSchema = systemSchema; } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedViewSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedViewSchema.java index 06959afa44be..fb6ae9cae28d 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedViewSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedViewSchema.java @@ -21,6 +21,7 @@ import com.google.inject.Inject; import org.apache.calcite.schema.Schema; +import org.apache.druid.server.security.ResourceType; public class NamedViewSchema implements NamedSchema { @@ -28,7 +29,7 @@ public class NamedViewSchema implements NamedSchema private final ViewSchema viewSchema; @Inject - NamedViewSchema(ViewSchema viewSchema) + public NamedViewSchema(ViewSchema viewSchema) { this.viewSchema = viewSchema; } @@ -39,6 +40,12 @@ public String getSchemaName() return NAME; } + @Override + public String getSchemaResourceType(String resourceName) + { + return ResourceType.VIEW; + } + @Override public Schema getSchema() { diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/RootSchemaProvider.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/RootSchemaProvider.java index 595e7ae52295..0c6176335880 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/RootSchemaProvider.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/RootSchemaProvider.java @@ -19,14 +19,15 @@ package org.apache.druid.sql.calcite.schema; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.Provider; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.java.util.common.ISE; -import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -37,7 +38,7 @@ * * All the provided schema are added to the rootSchema. */ -public class RootSchemaProvider implements Provider +public class RootSchemaProvider implements Provider { private final Set namedSchemas; @@ -48,18 +49,20 @@ public class RootSchemaProvider implements Provider } @Override - public SchemaPlus get() + public DruidSchemaCatalog get() { - SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); - List schemaNames = namedSchemas.stream() - .map(NamedSchema::getSchemaName) - .collect(Collectors.toList()); - Set uniqueSchemaNames = new HashSet<>(schemaNames); - if (uniqueSchemaNames.size() < schemaNames.size()) { - throw new ISE("Found multiple schemas registered to the same name. " - + "The list of registered schemas are %s", schemaNames); + final SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); + final Map schemasByName = Maps.newHashMapWithExpectedSize(namedSchemas.size()); + for (NamedSchema schema : namedSchemas) { + if (schemasByName.containsKey(schema.getSchemaName())) { + throw new ISE( + "Found multiple schemas registered to the same name. The list of registered schemas are %s", + namedSchemas.stream().map(NamedSchema::getSchemaName).collect(Collectors.toList()) + ); + } + schemasByName.put(schema.getSchemaName(), schema); + rootSchema.add(schema.getSchemaName(), schema.getSchema()); } - namedSchemas.forEach(schema -> rootSchema.add(schema.getSchemaName(), schema.getSchema())); - return rootSchema; + return new DruidSchemaCatalog(rootSchema, ImmutableMap.copyOf(schemasByName)); } } diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java index 3278bb6a80f3..3934665da486 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java @@ -30,13 +30,13 @@ import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Module; +import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.apache.calcite.avatica.AvaticaClientRuntimeException; import org.apache.calcite.avatica.Meta; import org.apache.calcite.avatica.MissingResultsException; import org.apache.calcite.avatica.NoSuchStatementException; import org.apache.calcite.avatica.server.AbstractAvaticaHandler; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.guice.GuiceInjectors; import org.apache.druid.guice.LazySingleton; @@ -64,7 +64,9 @@ import org.apache.druid.sql.calcite.planner.DruidOperatorTable; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.schema.DruidSchemaName; +import org.apache.druid.sql.calcite.schema.NamedSchema; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.QueryLogHook; @@ -168,7 +170,7 @@ public void setUp() throws Exception final PlannerConfig plannerConfig = new PlannerConfig(); final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); final ExprMacroTable macroTable = CalciteTests.createExprMacroTable(); - final SchemaPlus rootSchema = + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, CalciteTests.TEST_AUTHORIZER_MAPPER); testRequestLogger = new TestRequestLogger(); @@ -187,7 +189,10 @@ public void configure(Binder binder) binder.bind(AuthorizerMapper.class).toInstance(CalciteTests.TEST_AUTHORIZER_MAPPER); binder.bind(Escalator.class).toInstance(CalciteTests.TEST_AUTHENTICATOR_ESCALATOR); binder.bind(RequestLogger.class).toInstance(testRequestLogger); - binder.bind(SchemaPlus.class).toInstance(rootSchema); + binder.bind(DruidSchemaCatalog.class).toInstance(rootSchema); + for (NamedSchema schema : rootSchema.getNamedSchemas().values()) { + Multibinder.newSetBinder(binder, NamedSchema.class).addBinding().toInstance(schema); + } binder.bind(QueryLifecycleFactory.class) .toInstance(CalciteTests.createMockQueryLifecycleFactory(walker, conglomerate)); binder.bind(DruidOperatorTable.class).toInstance(operatorTable); @@ -865,7 +870,7 @@ public int getMaxRowsPerFrame() final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); final ExprMacroTable macroTable = CalciteTests.createExprMacroTable(); final List frames = new ArrayList<>(); - SchemaPlus rootSchema = + DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); DruidMeta smallFrameDruidMeta = new DruidMeta( CalciteTests.createSqlLifecycleFactory( @@ -954,7 +959,7 @@ public int getMinRowsPerFrame() final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); final ExprMacroTable macroTable = CalciteTests.createExprMacroTable(); final List frames = new ArrayList<>(); - SchemaPlus rootSchema = + DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); DruidMeta smallFrameDruidMeta = new DruidMeta( CalciteTests.createSqlLifecycleFactory( diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java index 44002866a32c..fbfc2f1010c6 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java @@ -23,7 +23,6 @@ import com.google.common.collect.Lists; import org.apache.calcite.avatica.ColumnMetaData; import org.apache.calcite.avatica.Meta; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.io.Closer; @@ -36,6 +35,7 @@ import org.apache.druid.sql.calcite.planner.DruidOperatorTable; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.QueryLogHook; @@ -87,7 +87,7 @@ public void setUp() throws Exception final PlannerConfig plannerConfig = new PlannerConfig(); final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); final ExprMacroTable macroTable = CalciteTests.createExprMacroTable(); - SchemaPlus rootSchema = + DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(conglomerate, walker, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); final PlannerFactory plannerFactory = new PlannerFactory( rootSchema, diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java index 876ea5c91443..45f0eb1fb160 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.calcite.plan.RelOptPlanner; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.annotations.UsedByJUnitParamsRunner; import org.apache.druid.common.config.NullHandling; import org.apache.druid.hll.VersionOneHyperLogLogCollector; @@ -80,6 +79,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.planner.PlannerFactory; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.QueryLogHook; @@ -896,7 +896,7 @@ public SqlLifecycleFactory getSqlLifecycleFactory( { final InProcessViewManager viewManager = new InProcessViewManager(CalciteTests.TEST_AUTHENTICATOR_ESCALATOR, CalciteTests.DRUID_VIEW_MACRO_FACTORY); - SchemaPlus rootSchema = CalciteTests.createMockRootSchema( + DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema( conglomerate, walker, plannerConfig, diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java index de788fadc7b9..337ffd24cbbf 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.tools.RelConversionException; import org.apache.calcite.tools.ValidationException; @@ -45,6 +44,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; import org.apache.druid.sql.calcite.planner.PlannerResult; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import org.apache.druid.testing.InitializedNullHandlingTest; @@ -128,7 +128,7 @@ public static void setupClass() CLOSER.register(WALKER); final PlannerConfig plannerConfig = new PlannerConfig(); - final SchemaPlus rootSchema = + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema(CONGLOMERATE, WALKER, plannerConfig, AuthTestUtils.TEST_AUTHORIZER_MAPPER); PLANNER_FACTORY = new PlannerFactory( rootSchema, diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java index dc56fe7bd711..3c049b4c3597 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java @@ -26,6 +26,7 @@ import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; +import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.SqlIntervalQualifier; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.type.SqlTypeName; @@ -45,8 +46,14 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.rel.VirtualColumnRegistry; +import org.apache.druid.sql.calcite.schema.DruidSchema; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; +import org.apache.druid.sql.calcite.schema.NamedDruidSchema; +import org.apache.druid.sql.calcite.schema.NamedViewSchema; +import org.apache.druid.sql.calcite.schema.ViewSchema; import org.apache.druid.sql.calcite.table.RowSignatures; import org.apache.druid.sql.calcite.util.CalciteTests; +import org.easymock.EasyMock; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.Assert; @@ -67,6 +74,13 @@ class ExpressionTestHelper CalciteTests.createOperatorTable(), CalciteTests.createExprMacroTable(), new PlannerConfig(), + new DruidSchemaCatalog( + EasyMock.createMock(SchemaPlus.class), + ImmutableMap.of( + "druid", new NamedDruidSchema(EasyMock.createMock(DruidSchema.class), "druid"), + NamedViewSchema.NAME, new NamedViewSchema(EasyMock.createMock(ViewSchema.class)) + ) + ), ImmutableMap.of() ); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java index e081e1fd0617..90dfcbada486 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java @@ -26,14 +26,15 @@ import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.guice.LazySingleton; import org.apache.druid.jackson.JacksonModule; import org.apache.druid.math.expr.ExprMacroTable; import org.apache.druid.server.QueryLifecycleFactory; import org.apache.druid.server.security.AuthorizerMapper; +import org.apache.druid.server.security.ResourceType; import org.apache.druid.sql.calcite.aggregation.SqlAggregator; import org.apache.druid.sql.calcite.expression.SqlOperatorConversion; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.schema.DruidSchemaName; import org.apache.druid.sql.calcite.schema.NamedSchema; import org.apache.druid.sql.calcite.util.CalciteTestBase; @@ -71,11 +72,10 @@ public class CalcitePlannerModuleTest extends CalciteTestBase @Mock private AuthorizerMapper authorizerMapper; @Mock - private SchemaPlus rootSchema; + private DruidSchemaCatalog rootSchema; private Set aggregators; private Set operatorConversions; - private Set calciteSchemas; private CalcitePlannerModule target; private Injector injector; @@ -87,8 +87,9 @@ public void setUp() EasyMock.expect(druidSchema2.getSchema()).andStubReturn(schema2); EasyMock.expect(druidSchema1.getSchemaName()).andStubReturn(SCHEMA_1); EasyMock.expect(druidSchema2.getSchemaName()).andStubReturn(SCHEMA_2); + EasyMock.expect(druidSchema1.getSchemaResourceType(EasyMock.anyString())).andStubReturn(ResourceType.DATASOURCE); + EasyMock.expect(druidSchema2.getSchemaResourceType(EasyMock.anyString())).andStubReturn("test"); EasyMock.replay(druidSchema1, druidSchema2); - calciteSchemas = ImmutableSet.of(druidSchema1, druidSchema2); aggregators = ImmutableSet.of(); operatorConversions = ImmutableSet.of(); target = new CalcitePlannerModule(); @@ -97,14 +98,13 @@ public void setUp() binder -> { binder.bind(Validator.class).toInstance(Validation.buildDefaultValidatorFactory().getValidator()); binder.bindScope(LazySingleton.class, Scopes.SINGLETON); - binder.bind(Key.get(new TypeLiteral>(){})).toInstance(calciteSchemas); binder.bind(QueryLifecycleFactory.class).toInstance(queryLifecycleFactory); binder.bind(ExprMacroTable.class).toInstance(macroTable); binder.bind(AuthorizerMapper.class).toInstance(authorizerMapper); binder.bind(String.class).annotatedWith(DruidSchemaName.class).toInstance(DRUID_SCHEMA_NAME); binder.bind(Key.get(new TypeLiteral>(){})).toInstance(aggregators); binder.bind(Key.get(new TypeLiteral>(){})).toInstance(operatorConversions); - binder.bind(SchemaPlus.class).toInstance(rootSchema); + binder.bind(DruidSchemaCatalog.class).toInstance(rootSchema); }, target ); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java index f225fe626269..451e16f4c166 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java @@ -28,7 +28,6 @@ import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.client.InventoryView; import org.apache.druid.client.TimelineServerView; import org.apache.druid.client.coordinator.Coordinator; @@ -203,12 +202,12 @@ public void testLookupSchemaIsInjectedAsSingleton() @Test public void testRootSchemaAnnotatedIsInjectedAsSingleton() { - SchemaPlus rootSchema = injector.getInstance( - Key.get(SchemaPlus.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) + DruidSchemaCatalog rootSchema = injector.getInstance( + Key.get(DruidSchemaCatalog.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) ); Assert.assertNotNull(rootSchema); - SchemaPlus other = injector.getInstance( - Key.get(SchemaPlus.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) + DruidSchemaCatalog other = injector.getInstance( + Key.get(DruidSchemaCatalog.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) ); Assert.assertSame(other, rootSchema); } @@ -216,10 +215,10 @@ public void testRootSchemaAnnotatedIsInjectedAsSingleton() @Test public void testRootSchemaIsInjectedAsSingleton() { - SchemaPlus rootSchema = injector.getInstance(Key.get(SchemaPlus.class)); + DruidSchemaCatalog rootSchema = injector.getInstance(Key.get(DruidSchemaCatalog.class)); Assert.assertNotNull(rootSchema); - SchemaPlus other = injector.getInstance( - Key.get(SchemaPlus.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) + DruidSchemaCatalog other = injector.getInstance( + Key.get(DruidSchemaCatalog.class, Names.named(DruidCalciteSchemaModule.INCOMPLETE_SCHEMA)) ); Assert.assertSame(other, rootSchema); } @@ -227,7 +226,7 @@ public void testRootSchemaIsInjectedAsSingleton() @Test public void testRootSchemaIsInjectedAndHasInformationSchema() { - SchemaPlus rootSchema = injector.getInstance(Key.get(SchemaPlus.class)); + DruidSchemaCatalog rootSchema = injector.getInstance(Key.get(DruidSchemaCatalog.class)); InformationSchema expectedSchema = injector.getInstance(InformationSchema.class); Assert.assertNotNull(rootSchema); Assert.assertSame(expectedSchema, rootSchema.getSubSchema("INFORMATION_SCHEMA").unwrap(InformationSchema.class)); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java index 71f116997934..6cfe3f2c81a2 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableSet; import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.java.util.common.ISE; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.easymock.EasyMock; @@ -72,9 +71,9 @@ public void setUp() @Test public void testGetShouldReturnRootSchemaWithProvidedSchemasRegistered() { - SchemaPlus rootSchema = target.get(); - Assert.assertEquals("", rootSchema.getName()); - Assert.assertFalse(rootSchema.isCacheEnabled()); + DruidSchemaCatalog rootSchema = target.get(); + Assert.assertEquals("", rootSchema.getRootSchema().getName()); + Assert.assertFalse(rootSchema.getRootSchema().isCacheEnabled()); // metadata schema should not be added Assert.assertEquals(druidSchemas.size(), rootSchema.getSubSchemaNames().size()); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java index 4e394825ee4a..a5e493d7f858 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java @@ -115,9 +115,15 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerFactory; import org.apache.druid.sql.calcite.schema.DruidSchema; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.schema.InformationSchema; import org.apache.druid.sql.calcite.schema.LookupSchema; import org.apache.druid.sql.calcite.schema.MetadataSegmentView; +import org.apache.druid.sql.calcite.schema.NamedDruidSchema; +import org.apache.druid.sql.calcite.schema.NamedLookupSchema; +import org.apache.druid.sql.calcite.schema.NamedSchema; +import org.apache.druid.sql.calcite.schema.NamedSystemSchema; +import org.apache.druid.sql.calcite.schema.NamedViewSchema; import org.apache.druid.sql.calcite.schema.SystemSchema; import org.apache.druid.sql.calcite.schema.ViewSchema; import org.apache.druid.sql.calcite.view.DruidViewMacroFactory; @@ -131,7 +137,6 @@ import org.joda.time.chrono.ISOChronology; import javax.annotation.Nullable; - import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -165,9 +170,6 @@ public class CalciteTests public static final String USERVISITDATASOURCE = "visits"; public static final String DRUID_SCHEMA_NAME = "druid"; public static final String INFORMATION_SCHEMA_NAME = "INFORMATION_SCHEMA"; - public static final String SYSTEM_SCHEMA_NAME = "sys"; - public static final String LOOKUP_SCHEMA_NAME = "lookup"; - public static final String VIEW_SCHEMA_NAME = "view"; public static final String TEST_SUPERUSER_NAME = "testSuperuser"; public static final AuthorizerMapper TEST_AUTHORIZER_MAPPER = new AuthorizerMapper(null) @@ -180,9 +182,9 @@ public Authorizer getAuthorizer(String name) return Access.OK; } - if (resource.getType() == ResourceType.DATASOURCE && resource.getName().equals(FORBIDDEN_DATASOURCE)) { + if (ResourceType.DATASOURCE.equals(resource.getType()) && resource.getName().equals(FORBIDDEN_DATASOURCE)) { return new Access(false); - } else if (resource.getType() == ResourceType.VIEW && resource.getName().equals("forbiddenView")) { + } else if (ResourceType.VIEW.equals(resource.getType()) && resource.getName().equals("forbiddenView")) { return new Access(false); } else { return Access.OK; @@ -1123,7 +1125,7 @@ public String findCurrentLeader() ); } - public static SchemaPlus createMockRootSchema( + public static DruidSchemaCatalog createMockRootSchema( final QueryRunnerFactoryConglomerate conglomerate, final SpecificSegmentsQuerySegmentWalker walker, final PlannerConfig plannerConfig, @@ -1134,22 +1136,31 @@ public static SchemaPlus createMockRootSchema( SystemSchema systemSchema = CalciteTests.createMockSystemSchema(druidSchema, walker, plannerConfig, authorizerMapper); + LookupSchema lookupSchema = CalciteTests.createMockLookupSchema(); SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); + Set namedSchemas = ImmutableSet.of( + new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), + new NamedSystemSchema(systemSchema), + new NamedLookupSchema(lookupSchema) + ); + DruidSchemaCatalog catalog = new DruidSchemaCatalog( + rootSchema, + namedSchemas.stream().collect(Collectors.toMap(NamedSchema::getSchemaName, x -> x)) + ); InformationSchema informationSchema = new InformationSchema( - rootSchema, - authorizerMapper, - CalciteTests.DRUID_SCHEMA_NAME + catalog, + authorizerMapper ); - LookupSchema lookupSchema = CalciteTests.createMockLookupSchema(); rootSchema.add(CalciteTests.DRUID_SCHEMA_NAME, druidSchema); rootSchema.add(CalciteTests.INFORMATION_SCHEMA_NAME, informationSchema); - rootSchema.add(CalciteTests.SYSTEM_SCHEMA_NAME, systemSchema); - rootSchema.add(CalciteTests.LOOKUP_SCHEMA_NAME, lookupSchema); - return rootSchema; + rootSchema.add(NamedSystemSchema.NAME, systemSchema); + rootSchema.add(NamedLookupSchema.NAME, lookupSchema); + + return catalog; } - public static SchemaPlus createMockRootSchema( + public static DruidSchemaCatalog createMockRootSchema( final QueryRunnerFactoryConglomerate conglomerate, final SpecificSegmentsQuerySegmentWalker walker, final PlannerConfig plannerConfig, @@ -1160,20 +1171,32 @@ public static SchemaPlus createMockRootSchema( DruidSchema druidSchema = createMockSchema(conglomerate, walker, plannerConfig, viewManager); SystemSchema systemSchema = CalciteTests.createMockSystemSchema(druidSchema, walker, plannerConfig, authorizerMapper); + + LookupSchema lookupSchema = CalciteTests.createMockLookupSchema(); + ViewSchema viewSchema = new ViewSchema(viewManager); + SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); + Set namedSchemas = ImmutableSet.of( + new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), + new NamedSystemSchema(systemSchema), + new NamedLookupSchema(lookupSchema), + new NamedViewSchema(viewSchema) + ); + DruidSchemaCatalog catalog = new DruidSchemaCatalog( + rootSchema, + namedSchemas.stream().collect(Collectors.toMap(NamedSchema::getSchemaName, x -> x)) + ); InformationSchema informationSchema = new InformationSchema( - rootSchema, - authorizerMapper, - CalciteTests.DRUID_SCHEMA_NAME + catalog, + authorizerMapper ); - LookupSchema lookupSchema = CalciteTests.createMockLookupSchema(); rootSchema.add(CalciteTests.DRUID_SCHEMA_NAME, druidSchema); rootSchema.add(CalciteTests.INFORMATION_SCHEMA_NAME, informationSchema); - rootSchema.add(CalciteTests.SYSTEM_SCHEMA_NAME, systemSchema); - rootSchema.add(CalciteTests.LOOKUP_SCHEMA_NAME, lookupSchema); - rootSchema.add(CalciteTests.VIEW_SCHEMA_NAME, new ViewSchema(viewManager)); - return rootSchema; + rootSchema.add(NamedSystemSchema.NAME, systemSchema); + rootSchema.add(NamedLookupSchema.NAME, lookupSchema); + rootSchema.add(NamedViewSchema.NAME, viewSchema); + return catalog; } /** diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java index 870342afb5c1..c7387ac6fb55 100644 --- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java +++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java @@ -28,7 +28,6 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.apache.calcite.avatica.SqlType; -import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.tools.RelConversionException; import org.apache.druid.common.config.NullHandling; import org.apache.druid.common.guava.SettableSupplier; @@ -71,6 +70,7 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.planner.PlannerFactory; +import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.QueryLogHook; @@ -184,7 +184,7 @@ public boolean shouldSerializeComplexValues() return false; } }; - final SchemaPlus rootSchema = CalciteTests.createMockRootSchema( + final DruidSchemaCatalog rootSchema = CalciteTests.createMockRootSchema( conglomerate, walker, plannerConfig,