blob: 169e78ddde419c8b631f0ca1b02033773b60c480 [file] [log] [blame]
/*
* 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.phoenix.schema;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import org.apache.hadoop.hbase.HConstants;
import org.apache.phoenix.parse.PFunction;
import org.apache.phoenix.parse.PSchema;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.util.ReadOnlyProps;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.util.TimeKeeper;
import com.google.common.collect.Lists;
/**
* Client-side cache of MetaData, not thread safe. Internally uses a LinkedHashMap that evicts the
* oldest entries when size grows beyond the maxSize specified at create time.
*/
public class PMetaDataImpl implements PMetaData {
private PMetaDataCache metaData;
private final TimeKeeper timeKeeper;
private final PTableRefFactory tableRefFactory;
public PMetaDataImpl(int initialCapacity, ReadOnlyProps props) {
this(initialCapacity, TimeKeeper.SYSTEM, props);
}
public PMetaDataImpl(int initialCapacity, TimeKeeper timeKeeper, ReadOnlyProps props) {
this(new PMetaDataCache(initialCapacity, props.getLong(
QueryServices.MAX_CLIENT_METADATA_CACHE_SIZE_ATTRIB,
QueryServicesOptions.DEFAULT_MAX_CLIENT_METADATA_CACHE_SIZE), timeKeeper,
PTableRefFactory.getFactory(props)), timeKeeper, PTableRefFactory.getFactory(props));
}
private PMetaDataImpl(PMetaDataCache metaData, TimeKeeper timeKeeper, PTableRefFactory tableRefFactory) {
this.timeKeeper = timeKeeper;
this.metaData = metaData;
this.tableRefFactory = tableRefFactory;
}
@Override
public PMetaDataImpl clone() {
return new PMetaDataImpl(new PMetaDataCache(this.metaData), this.timeKeeper, this.tableRefFactory);
}
@Override
public PTableRef getTableRef(PTableKey key) throws TableNotFoundException {
PTableRef ref = metaData.get(key);
if (ref == null) {
throw new TableNotFoundException(key.getName());
}
return ref;
}
@Override
public PFunction getFunction(PTableKey key) throws FunctionNotFoundException {
PFunction function = metaData.functions.get(key);
if (function == null) {
throw new FunctionNotFoundException(key.getName());
}
return function;
}
@Override
public int size() {
return metaData.size();
}
@Override
public void updateResolvedTimestamp(PTable table, long resolvedTimestamp) throws SQLException {
metaData.put(table.getKey(), tableRefFactory.makePTableRef(table, this.timeKeeper.getCurrentTime(), resolvedTimestamp));
}
@Override
public void addTable(PTable table, long resolvedTime) throws SQLException {
PTableRef tableRef = tableRefFactory.makePTableRef(table, this.timeKeeper.getCurrentTime(), resolvedTime);
int netGain = 0;
PTableKey key = table.getKey();
PTableRef oldTableRef = metaData.get(key);
if (oldTableRef != null) {
netGain -= oldTableRef.getEstimatedSize();
}
PTable newParentTable = null;
PTableRef newParentTableRef = null;
long parentResolvedTimestamp = resolvedTime;
if (table.getType() == PTableType.INDEX) { // Upsert new index table into parent data table list
String parentName = table.getParentName().getString();
PTableRef oldParentRef = metaData.get(new PTableKey(table.getTenantId(), parentName));
// If parentTable isn't cached, that's ok we can skip this
if (oldParentRef != null) {
List<PTable> oldIndexes = oldParentRef.getTable().getIndexes();
List<PTable> newIndexes = Lists.newArrayListWithExpectedSize(oldIndexes.size() + 1);
newIndexes.addAll(oldIndexes);
for (int i = 0; i < newIndexes.size(); i++) {
PTable index = newIndexes.get(i);
if (index.getName().equals(table.getName())) {
newIndexes.remove(i);
break;
}
}
newIndexes.add(table);
netGain -= oldParentRef.getEstimatedSize();
newParentTable = PTableImpl.makePTable(oldParentRef.getTable(), table.getTimeStamp(), newIndexes);
newParentTableRef = tableRefFactory.makePTableRef(newParentTable, this.timeKeeper.getCurrentTime(), parentResolvedTimestamp);
netGain += newParentTableRef.getEstimatedSize();
}
}
if (newParentTable == null) { // Don't count in gain if we found a parent table, as its accounted for in newParentTable
netGain += tableRef.getEstimatedSize();
}
long overage = metaData.getCurrentSize() + netGain - metaData.getMaxSize();
metaData = overage <= 0 ? metaData : metaData.cloneMinusOverage(overage);
if (newParentTable != null) { // Upsert new index table into parent data table list
metaData.put(newParentTable.getKey(), newParentTableRef);
metaData.put(table.getKey(), tableRef);
} else {
metaData.put(table.getKey(), tableRef);
}
for (PTable index : table.getIndexes()) {
metaData.put(index.getKey(), tableRefFactory.makePTableRef(index, this.timeKeeper.getCurrentTime(), resolvedTime));
}
}
@Override
public void removeTable(PName tenantId, String tableName, String parentTableName, long tableTimeStamp) throws SQLException {
PTableRef parentTableRef = null;
PTableKey key = new PTableKey(tenantId, tableName);
if (metaData.get(key) == null) {
if (parentTableName != null) {
parentTableRef = metaData.get(new PTableKey(tenantId, parentTableName));
}
if (parentTableRef == null) {
return;
}
} else {
PTable table = metaData.remove(key);
for (PTable index : table.getIndexes()) {
metaData.remove(index.getKey());
}
if (table.getParentName() != null) {
parentTableRef = metaData.get(new PTableKey(tenantId, table.getParentName().getString()));
}
}
// also remove its reference from parent table
if (parentTableRef != null) {
List<PTable> oldIndexes = parentTableRef.getTable().getIndexes();
if(oldIndexes != null && !oldIndexes.isEmpty()) {
List<PTable> newIndexes = Lists.newArrayListWithExpectedSize(oldIndexes.size());
newIndexes.addAll(oldIndexes);
for (int i = 0; i < newIndexes.size(); i++) {
PTable index = newIndexes.get(i);
if (index.getName().getString().equals(tableName)) {
newIndexes.remove(i);
PTable parentTable = PTableImpl.makePTable(
parentTableRef.getTable(),
tableTimeStamp == HConstants.LATEST_TIMESTAMP ? parentTableRef.getTable().getTimeStamp() : tableTimeStamp,
newIndexes);
metaData.put(parentTable.getKey(), tableRefFactory.makePTableRef(parentTable, this.timeKeeper.getCurrentTime(), parentTableRef.getResolvedTimeStamp()));
break;
}
}
}
}
}
@Override
public void removeColumn(PName tenantId, String tableName, List<PColumn> columnsToRemove, long tableTimeStamp, long tableSeqNum, long resolvedTime) throws SQLException {
PTableRef tableRef = metaData.get(new PTableKey(tenantId, tableName));
if (tableRef == null) {
return;
}
PTable table = tableRef.getTable();
PMetaDataCache tables = metaData;
for (PColumn columnToRemove : columnsToRemove) {
PColumn column;
String familyName = columnToRemove.getFamilyName().getString();
if (familyName == null) {
column = table.getPKColumn(columnToRemove.getName().getString());
} else {
column = table.getColumnFamily(familyName).getPColumnForColumnName(columnToRemove.getName().getString());
}
int positionOffset = 0;
int position = column.getPosition();
List<PColumn> oldColumns = table.getColumns();
if (table.getBucketNum() != null) {
position--;
positionOffset = 1;
oldColumns = oldColumns.subList(positionOffset, oldColumns.size());
}
List<PColumn> columns = Lists.newArrayListWithExpectedSize(oldColumns.size() - 1);
columns.addAll(oldColumns.subList(0, position));
// Update position of columns that follow removed column
for (int i = position+1; i < oldColumns.size(); i++) {
PColumn oldColumn = oldColumns.get(i);
PColumn newColumn = new PColumnImpl(oldColumn.getName(), oldColumn.getFamilyName(), oldColumn.getDataType(), oldColumn.getMaxLength(), oldColumn.getScale(), oldColumn.isNullable(), i-1+positionOffset, oldColumn.getSortOrder(), oldColumn.getArraySize(), oldColumn.getViewConstant(), oldColumn.isViewReferenced(), oldColumn.getExpressionStr(), oldColumn.isRowTimestamp(), oldColumn.isDynamic(), oldColumn.getColumnQualifierBytes());
columns.add(newColumn);
}
table = PTableImpl.makePTable(table, tableTimeStamp, tableSeqNum, columns);
}
tables.put(table.getKey(), tableRefFactory.makePTableRef(table, this.timeKeeper.getCurrentTime(), resolvedTime));
}
@Override
public void pruneTables(Pruner pruner) {
List<PTableKey> keysToPrune = Lists.newArrayListWithExpectedSize(this.size());
for (PTable table : this) {
if (pruner.prune(table)) {
keysToPrune.add(table.getKey());
}
}
if (!keysToPrune.isEmpty()) {
for (PTableKey key : keysToPrune) {
metaData.remove(key);
}
}
}
@Override
public Iterator<PTable> iterator() {
return metaData.iterator();
}
@Override
public void addFunction(PFunction function) throws SQLException {
this.metaData.functions.put(function.getKey(), function);
}
@Override
public void removeFunction(PName tenantId, String function, long functionTimeStamp)
throws SQLException {
this.metaData.functions.remove(new PTableKey(tenantId, function));
}
@Override
public void pruneFunctions(Pruner pruner) {
List<PTableKey> keysToPrune = Lists.newArrayListWithExpectedSize(this.size());
for (PFunction function : this.metaData.functions.values()) {
if (pruner.prune(function)) {
keysToPrune.add(function.getKey());
}
}
if (!keysToPrune.isEmpty()) {
for (PTableKey key : keysToPrune) {
metaData.functions.remove(key);
}
}
}
@Override
public long getAge(PTableRef ref) {
return this.metaData.getAge(ref);
}
@Override
public void addSchema(PSchema schema) throws SQLException {
this.metaData.schemas.put(schema.getSchemaKey(), schema);
}
@Override
public PSchema getSchema(PTableKey key) throws SchemaNotFoundException {
PSchema schema = metaData.schemas.get(key);
if (schema == null) { throw new SchemaNotFoundException(key.getName()); }
return schema;
}
@Override
public void removeSchema(PSchema schema, long schemaTimeStamp) {
this.metaData.schemas.remove(SchemaUtil.getSchemaKey(schema.getSchemaName()));
}
}