/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.metadata.util; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import javax.inject.Inject; import org.apache.thrift.TException; import org.diqube.cluster.ClusterLayout; import org.diqube.connection.ConnectionException; import org.diqube.connection.ConnectionOrLocalHelper; import org.diqube.connection.ServiceProvider; import org.diqube.consensus.ConsensusClient.ConsensusClusterUnavailableException; import org.diqube.context.AutoInstatiate; import org.diqube.diql.DiqlParseUtil; import org.diqube.diql.antlr.DiqlParser.DiqlStmtContext; import org.diqube.diql.request.ExecutionRequest; import org.diqube.diql.visitors.SelectStmtVisitor; import org.diqube.name.FlattenedTableNameUtil; import org.diqube.name.FunctionBasedColumnNameBuilderFactory; import org.diqube.name.RepeatedColumnNameGenerator; import org.diqube.remote.cluster.thrift.ClusterFlattenService; import org.diqube.remote.cluster.thrift.ROptionalUuid; import org.diqube.thrift.base.thrift.AuthorizationException; import org.diqube.thrift.base.thrift.RNodeAddress; import org.diqube.thrift.base.util.RUuidUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class to help find out the currently valid table name of a flattened table where no flattenId is available * yet. * * @author Bastian Gloeckle */ @AutoInstatiate public class CurrentFlattenedTableNameUtil { private static final Logger logger = LoggerFactory.getLogger(CurrentFlattenedTableNameUtil.class); @Inject private RepeatedColumnNameGenerator repeatedColumnNameGenerator; @Inject private FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory; @Inject private ConnectionOrLocalHelper connectionOrLocalHelper; @Inject private ClusterLayout clusterLayout; @Inject private FlattenedTableNameUtil flattenedTableNameUtil; /** * Takes a table "name" of the form "flatten(TABLE, FIELD)", i.e. without the flattenId, and finds out the potentially * newest flattenId for it and returns a valid table name including that ID. The returned table name can be used to * resolve table metadata. * * @throws FlattenIdentificationImpossibleException * If no currently valid table name can be identified. * @throws AuthorizationException * in case either the original table does not exist - in that case an {@link AuthorizationException} should * be reported in order to not let the user know if the table actually does not exist or if he simply does * not have access to it. */ public String enhanceIncompleteFlattenedTableNameWithNewestFlattenId(String tableName) throws FlattenIdentificationImpossibleException, AuthorizationException { // provided table name is of the form flatten(TABLE, FIELD), i.e. without the flattenId. Append newest. RuntimeException error = null; ExecutionRequest req = null; try { DiqlStmtContext stmt = DiqlParseUtil.parseWithAntlr("select a from " + tableName); req = stmt.accept(new SelectStmtVisitor(repeatedColumnNameGenerator, functionBasedColumnNameBuilderFactory)); if (!req.getFromRequest().isFlattened()) error = new RuntimeException("Unkown table name, do not know how to handle it: " + tableName); } catch (RuntimeException e) { error = e; } if (error != null) { logger.warn("Could not parse table information '{}'.", tableName, error); throw new FlattenIdentificationImpossibleException("Could not parse table information '" + tableName + "'."); } String origTable = req.getFromRequest().getTable(); String flattenBy = req.getFromRequest().getFlattenByField(); Collection<RNodeAddress> nodes; try { nodes = clusterLayout.findNodesServingTable(origTable); } catch (InterruptedException | ConsensusClusterUnavailableException e) { throw new FlattenIdentificationImpossibleException( "Could not find nodes serving table " + origTable + " for finding metadata of '" + tableName + "'", e); } if (nodes.isEmpty()) { logger.warn("No nodes serving '{}'.", origTable); throw new AuthorizationException("No access to table or table does not exist."); } RNodeAddress node = new ArrayList<>(nodes).get(ThreadLocalRandom.current().nextInt(nodes.size())); UUID flattenId = null; try (ServiceProvider<ClusterFlattenService.Iface> sp = connectionOrLocalHelper.getService(ClusterFlattenService.Iface.class, node, null)) { ROptionalUuid optId = sp.getService().getLatestValidFlattening(origTable, flattenBy); if (optId.isSetUuid()) flattenId = RUuidUtil.toUuid(optId.getUuid()); } catch (IOException | ConnectionException | InterruptedException | TException | RuntimeException e) { logger.warn("Could not find newest flattenId for flattening of table {} by {} from {}", origTable, flattenBy, node, e); throw new FlattenIdentificationImpossibleException("Could not find out current flattenId", e); } logger.debug("Chosing flattenId {} for table '{}'", flattenId, tableName); return flattenedTableNameUtil.createFlattenedTableName(origTable, flattenBy, flattenId); } public static class FlattenIdentificationImpossibleException extends Exception { private static final long serialVersionUID = 1L; public FlattenIdentificationImpossibleException(String msg) { super(msg); } public FlattenIdentificationImpossibleException(String msg, Throwable cause) { super(msg, cause); } } }