/** * 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.create; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.diqube.data.column.ColumnShard; import org.diqube.data.table.TableShard; import org.diqube.name.RepeatedColumnNameGenerator; import org.diqube.thrift.base.thrift.FieldMetadata; import org.diqube.thrift.base.thrift.FieldType; import org.diqube.thrift.base.thrift.TableMetadata; import org.diqube.util.Pair; /** * Creates {@link TableShardMetadata} based on a single local {@link TableShard}. * * @author Bastian Gloeckle */ public class TableShardMetadataBuilder { private TableShard tableShard; private RepeatedColumnNameGenerator repeatedColumnNameGenerator; public TableShardMetadataBuilder(RepeatedColumnNameGenerator repeatedColumnNameGenerator) { this.repeatedColumnNameGenerator = repeatedColumnNameGenerator; } public TableShardMetadataBuilder from(TableShard tableShard) { this.tableShard = tableShard; return this; } public TableMetadata build() throws IllegalTableShardLayoutException { Map<String, Pair<FieldType, Boolean>> fields = new HashMap<>(); for (ColumnShard colShard : tableShard.getColumns().values()) { if (FieldUtil.columnTypeMightDifferFromFieldType(colShard.getName())) // ignore type of length columns - we're only interested in the type of the columns that contain actual data. continue; String fieldName = FieldUtil.toFieldName(colShard.getName()); FieldType fieldType = FieldUtil.toFieldType(colShard.getColumnType()); boolean repeated = colShard.getName().endsWith(repeatedColumnNameGenerator.repeatedColumnNameEndsWith()); Pair<FieldType, Boolean> newFieldInfo = new Pair<>(fieldType, repeated); safePutFields(fieldName, newFieldInfo, fields); for (Pair<String, Boolean> parentField : allParentFields(colShard.getName())) { Pair<FieldType, Boolean> parentFieldInfo = new Pair<>(FieldType.CONTAINER, parentField.getRight()); safePutFields(parentField.getLeft(), parentFieldInfo, fields); } } // create final objects List<FieldMetadata> resFields = new ArrayList<>(); for (Entry<String, Pair<FieldType, Boolean>> e : fields.entrySet()) resFields.add(new FieldMetadata(e.getKey(), e.getValue().getLeft(), e.getValue().getRight())); return new TableMetadata(tableShard.getTableName(), resFields); } /** * @return Collection of parent field name and information if parent field seems to be repeated. Created from the * column name. */ private Collection<Pair<String, Boolean>> allParentFields(String columnName) { List<Pair<String, Boolean>> res = new ArrayList<>(); String[] allParts = columnName.split("\\."); String last = null; for (int i = 0; i < allParts.length - 1 /* leave last one out, since we only want parents */; i++) { String cur; if (last == null) cur = allParts[i]; else cur = last + "." + allParts[i]; boolean repeated = cur.endsWith(repeatedColumnNameGenerator.repeatedColumnNameEndsWith()); cur = FieldUtil.toFieldName(cur); res.add(new Pair<>(cur, repeated)); last = cur; } return res; } /** * Put new field info into map, but first check if the map contains different information first and throw information * if that is the case. */ private void safePutFields(String fieldName, Pair<FieldType, Boolean> newFieldInfo, Map<String, Pair<FieldType, Boolean>> target) throws IllegalTableShardLayoutException { if (target.containsKey(fieldName) && !newFieldInfo.equals(target.get(fieldName))) throw new IllegalTableShardLayoutException("Field " + fieldName + " has incompatible types in shard " + tableShard.getLowestRowId() + ": " + target.get(fieldName) + " <-> " + newFieldInfo); target.putIfAbsent(fieldName, newFieldInfo); } /** * The TableShardMetadata cannot be created because the TableShard contains invalid data. * * @author Bastian Gloeckle */ public static class IllegalTableShardLayoutException extends Exception { private static final long serialVersionUID = 1L; private IllegalTableShardLayoutException(String msg) { super(msg); } private IllegalTableShardLayoutException(String msg, Throwable cause) { super(msg, cause); } } }