/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program 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 com.foundationdb.server.rowdata; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.GroupIndex; import com.foundationdb.ais.model.TableIndex; import com.foundationdb.qp.virtualadapter.VirtualAdapter; import com.foundationdb.qp.virtualadapter.VirtualScanFactory; import com.foundationdb.server.TableStatusCache; import com.foundationdb.server.service.session.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.IndexColumn; import com.foundationdb.ais.model.Join; import com.foundationdb.ais.model.JoinColumn; import com.foundationdb.ais.model.Table; public class RowDefBuilder { private static final Logger LOG = LoggerFactory.getLogger(RowDefBuilder.class.getName()); /** Should <b>only</b> be used for debugging (e.g. friendly toString). This view is not transaction safe. **/ public static volatile AkibanInformationSchema LATEST_FOR_DEBUGGING; private final AkibanInformationSchema ais; public RowDefBuilder(Session session, AkibanInformationSchema ais, TableStatusCache tableStatusCache) { this.ais = ais; LATEST_FOR_DEBUGGING = ais; } public void build() { Map<Integer, RowDef> newRowDefs = new TreeMap<>(); for (final Table table : ais.getTables().values()) { RowDef rowDef = createTableRowDef(table); Integer key = rowDef.getRowDefId(); RowDef prev = newRowDefs.put(key, rowDef); if (prev != null) { throw new IllegalStateException("Duplicate RowDefID (" + key + ") for RowDef: " + rowDef); } } Map<Table,Integer> ordinalMap = createOrdinalMap(); for (RowDef rowDef : newRowDefs.values()) { rowDef.computeFieldAssociations(ordinalMap); } LOG.trace("Built RowDefs: {}", this); } private RowDef createRowDefCommon(Table table, VirtualScanFactory factory) { return new RowDef(table); // Hooks up table's rowDef too } /** @return Map of Table->Ordinal for all Tables/RowDefs in the AIS */ protected Map<Table,Integer> createOrdinalMap() { Map<Table,Integer> ordinalMap = new HashMap<>(); for(Table table : ais.getTables().values()) { Integer ordinal = table.getOrdinal(); assert ordinal != null : "Null ordinal: " + table; ordinalMap.put(table, ordinal); } return ordinalMap; } private RowDef createTableRowDef(Table table) { RowDef rowDef = createRowDefCommon(table, table.isVirtual() ? VirtualAdapter.getFactory(table) : null); // parentRowDef int[] parentJoinFields; if (table.getParentJoin() != null) { final Join join = table.getParentJoin(); parentJoinFields = new int[join.getJoinColumns().size()]; for (int index = 0; index < join.getJoinColumns().size(); index++) { final JoinColumn joinColumn = join.getJoinColumns().get(index); parentJoinFields[index] = joinColumn.getChild().getPosition(); } } else { parentJoinFields = new int[0]; } // root table Table root = table; while (root.getParentJoin() != null) { root = root.getParentJoin().getParent(); } // Secondary indexes List<TableIndex> indexList = new ArrayList<>(); for (TableIndex index : table.getIndexesIncludingInternal()) { List<IndexColumn> indexColumns = index.getKeyColumns(); if(!indexColumns.isEmpty()) { if (index.isPrimaryKey()) { indexList.add(0, index); } else { indexList.add(index); } } //else Don't create entry for empty, autogenerated indexes } // Group indexes final List<GroupIndex> groupIndexList = new ArrayList<>(); for (GroupIndex index : table.getGroupIndexes()) { if(index.leafMostTable() == table) { groupIndexList.add(index); } } rowDef.setParentJoinFields(parentJoinFields); rowDef.setIndexes(indexList); rowDef.setGroupIndexes(groupIndexList.toArray(new GroupIndex[groupIndexList.size()])); return rowDef; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); for (Table table : ais.getTables().values()) { if(sb.length() > 0) { sb.append("\n"); } sb.append(" "); sb.append(table.rowDef().toString()); } return sb.toString(); } protected Map<Group,List<RowDef>> getRowDefsByGroup() { Map<Group,List<RowDef>> groupToRowDefs = new HashMap<>(); for(Table table : ais.getTables().values()) { RowDef rowDef = table.rowDef(); List<RowDef> list = groupToRowDefs.get(rowDef.getGroup()); if(list == null) { list = new ArrayList<>(); groupToRowDefs.put(rowDef.getGroup(), list); } list.add(rowDef); } // NB: Ordinals should be increasing from root to leaf. Sort ensures that. for(List<RowDef> rowDefs : groupToRowDefs.values()) { Collections.sort(rowDefs, ROWDEF_DEPTH_COMPARATOR); } return groupToRowDefs; } /** By group depth and then qualified table name for determinism **/ private static Comparator<RowDef> ROWDEF_DEPTH_COMPARATOR = new Comparator<RowDef>() { @Override public int compare(RowDef o1, RowDef o2) { int cmp = o1.table().getDepth().compareTo(o2.table().getDepth()); if(cmp == 0) { cmp = o1.table().getName().compareTo(o2.table().getName()); } return cmp; } }; }