/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.metadata; import com.carrotsearch.hppc.cursors.ObjectCursor; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; import io.crate.exceptions.SchemaUnknownException; import io.crate.exceptions.TableUnknownException; import io.crate.metadata.blob.BlobSchemaInfo; import io.crate.metadata.doc.DocSchemaInfoFactory; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.information.InformationSchemaInfo; import io.crate.metadata.sys.SysSchemaInfo; import io.crate.metadata.table.Operation; import io.crate.metadata.table.SchemaInfo; import io.crate.metadata.table.TableInfo; import io.crate.operation.udf.UserDefinedFunctionMetaData; import io.crate.operation.udf.UserDefinedFunctionsMetaData; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Singleton; import org.elasticsearch.common.settings.Settings; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.google.common.base.MoreObjects.firstNonNull; @Singleton public class Schemas extends AbstractLifecycleComponent implements Iterable<SchemaInfo>, ClusterStateListener { public static final Pattern SCHEMA_PATTERN = Pattern.compile("^([^.]+)\\.(.+)"); public static final String DEFAULT_SCHEMA_NAME = "doc"; private final ClusterService clusterService; private final DocSchemaInfoFactory docSchemaInfoFactory; private final Map<String, SchemaInfo> schemas = new ConcurrentHashMap<>(); private final Map<String, SchemaInfo> builtInSchemas; private final DefaultTemplateService defaultTemplateService; @Inject public Schemas(Settings settings, Map<String, SchemaInfo> builtInSchemas, ClusterService clusterService, DocSchemaInfoFactory docSchemaInfoFactory) { super(settings); this.clusterService = clusterService; this.docSchemaInfoFactory = docSchemaInfoFactory; schemas.putAll(builtInSchemas); this.builtInSchemas = builtInSchemas; this.defaultTemplateService = new DefaultTemplateService(settings, clusterService); } /** * @param ident the table ident to get a TableInfo for * @return an instance of TableInfo for the given ident, guaranteed to be not null * @throws io.crate.exceptions.SchemaUnknownException if schema given in <code>ident</code> * does not exist * @throws io.crate.exceptions.TableUnknownException if table given in <code>ident</code> does * not exist in the given schema */ public <T extends TableInfo> T getTableInfo(TableIdent ident) { SchemaInfo schemaInfo = getSchemaInfo(ident); TableInfo info; info = schemaInfo.getTableInfo(ident.name()); if (info == null) { throw new TableUnknownException(ident); } return (T) info; } /** * @param ident the table ident to get a TableInfo for * @param operation The opreation planned to be performed on the table * @return an instance of TableInfo for the given ident, guaranteed to be not null and to support the operation * required on it. * @throws io.crate.exceptions.SchemaUnknownException if schema given in <code>ident</code> * does not exist * @throws io.crate.exceptions.TableUnknownException if table given in <code>ident</code> does * not exist in the given schema */ public <T extends TableInfo> T getTableInfo(TableIdent ident, Operation operation) { TableInfo tableInfo = getTableInfo(ident); Operation.blockedRaiseException(tableInfo, operation); return (T) tableInfo; } private SchemaInfo getSchemaInfo(TableIdent ident) { String schemaName = firstNonNull(ident.schema(), DEFAULT_SCHEMA_NAME); SchemaInfo schemaInfo = schemas.get(schemaName); if (schemaInfo == null) { throw new SchemaUnknownException(schemaName); } return schemaInfo; } @Nonnull public Iterator<SchemaInfo> iterator() { return schemas.values().iterator(); } @Override public void clusterChanged(ClusterChangedEvent event) { if (!event.metaDataChanged()) { return; } defaultTemplateService.createIfNotExists(event.state()); Set<String> newCurrentSchemas = getNewCurrentSchemas(event.state().metaData()); synchronized (schemas) { Sets.SetView<String> nonBuiltInSchemas = Sets.difference(schemas.keySet(), builtInSchemas.keySet()); Set<String> deleted = Sets.difference(nonBuiltInSchemas, newCurrentSchemas).immutableCopy(); Set<String> added = Sets.difference(newCurrentSchemas, schemas.keySet()).immutableCopy(); for (String deletedSchema : deleted) { try { schemas.remove(deletedSchema).close(); } catch (Exception e) { logger.error(e.getMessage(), e); } } for (String addedSchema : added) { schemas.put(addedSchema, getCustomSchemaInfo(addedSchema)); } // update all existing schemas for (SchemaInfo schemaInfo : this) { schemaInfo.update(event); } } } @VisibleForTesting static Set<String> getNewCurrentSchemas(MetaData metaData) { Set<String> schemas = new HashSet<>(); for (String openIndex : metaData.getConcreteAllOpenIndices()) { addIfSchema(schemas, openIndex); } for (ObjectCursor<String> cursor : metaData.templates().keys()) { addIfSchema(schemas, cursor.value); } UserDefinedFunctionsMetaData udfMetaData = metaData.custom(UserDefinedFunctionsMetaData.TYPE); if (udfMetaData != null) { udfMetaData.functionsMetaData() .stream() .map(UserDefinedFunctionMetaData::schema) .forEach(schemas::add); } return schemas; } private static void addIfSchema(Set<String> schemas, String indexOrTemplate) { Matcher matcher = SCHEMA_PATTERN.matcher(indexOrTemplate); if (matcher.matches()) { schemas.add(matcher.group(1)); } else { schemas.add(DEFAULT_SCHEMA_NAME); } } /** * Create a custom schema info. * * @param name The schema name * @return an instance of SchemaInfo for the given name */ private SchemaInfo getCustomSchemaInfo(String name) { return docSchemaInfoFactory.create(name, clusterService); } /** * Checks if a given schema name string is a user defined schema or the default one. * * @param schemaName The schema name as a string. */ static boolean isDefaultOrCustomSchema(@Nullable String schemaName) { if (schemaName == null) { return true; } if (schemaName.equalsIgnoreCase(InformationSchemaInfo.NAME) || schemaName.equalsIgnoreCase(SysSchemaInfo.NAME) || schemaName.equalsIgnoreCase(BlobSchemaInfo.NAME) ) { return false; } return true; } public boolean tableExists(TableIdent tableIdent) { SchemaInfo schemaInfo = schemas.get(firstNonNull(tableIdent.schema(), DEFAULT_SCHEMA_NAME)); if (schemaInfo == null) { return false; } schemaInfo.invalidateTableCache(tableIdent.name()); TableInfo tableInfo = schemaInfo.getTableInfo(tableIdent.name()); if (tableInfo == null) { return false; } else if ((tableInfo instanceof DocTableInfo)) { return !isOrphanedAlias((DocTableInfo) tableInfo); } return true; } /** * checks if the given TableInfo has been created from an orphaned alias left from * an incomplete drop table on a partitioned table */ public boolean isOrphanedAlias(DocTableInfo table) { if (table.isPartitioned() && table.isAlias() && table.concreteIndices().length >= 1) { String templateName = PartitionName.templateName(table.ident().schema(), table.ident().name()); MetaData metaData = clusterService.state().metaData(); if (!metaData.templates().containsKey(templateName)) { // template or alias missing return true; } } return false; } @Override protected void doStart() { // add listener here to avoid guice proxy errors if the ClusterService could not be build clusterService.add(this); } @Override protected void doStop() { clusterService.remove(this); } @Override protected void doClose() { } }