/**
* 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.store;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.GroupIndex;
import com.foundationdb.ais.model.IndexRowComposition;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.rowtype.InternalIndexTypes;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.WriteIndexRow;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.common.BigDecimalWrapper;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.spatial.Spatial;
import com.foundationdb.util.ArgumentValidation;
import com.geophile.z.Space;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class StoreGIHandler<SType extends AbstractStore,SDType,SSDType extends StoreStorageDescription<SType,SDType>> {
private static final TInstance NON_NULL_Z_TYPE = InternalIndexTypes.LONG.instance(false);
private static final Logger LOG = LoggerFactory.getLogger(StoreGIHandler.class);
public static enum GroupIndexPosition {
ABOVE_SEGMENT,
BELOW_SEGMENT,
WITHIN_SEGMENT
}
public static enum Action {
STORE,
DELETE,
CASCADE,
CASCADE_STORE
}
private final AbstractStore<SType,SDType,SSDType> store;
private final Session session;
private final Table sourceTable;
private final WriteIndexRow indexRow;
private final Value zSource_t3 = new Value(InternalIndexTypes.LONG.instance(true));
private final Collection<RowType> lockTypes;
private StoreGIHandler(AbstractStore<SType,SDType,SSDType> store, Session session, Schema schema, Table sourceTable, Table lockLeaf) {
this.store = store;
this.session = session;
this.indexRow = new WriteIndexRow();
this.sourceTable = sourceTable;
if(lockLeaf == null) {
this.lockTypes = null;
} else {
this.lockTypes = new ArrayList<>(lockLeaf.getDepth());
for(Table table = lockLeaf; table != null; table = table.getParentTable()) {
lockTypes.add(schema.tableRowType(table));
}
}
}
public static <SType extends AbstractStore,SDType,SSDType extends StoreStorageDescription<SType,SDType>> StoreGIHandler forTable(AbstractStore<SType,SDType,SSDType> store,
Session session,
Table table) {
ArgumentValidation.notNull("table", table);
return new StoreGIHandler<>(store, session, null, table, null);
}
public static <SType extends AbstractStore,SDType,SSDType extends StoreStorageDescription<SType,SDType>> StoreGIHandler forBuilding(AbstractStore<SType,SDType,SSDType> store,
Session session,
Schema schema,
GroupIndex groupIndex) {
return new StoreGIHandler<>(store, session, schema, null, groupIndex.leafMostTable());
}
public void handleRow(GroupIndex groupIndex, Row row, Action action) {
GroupIndexPosition sourceRowPosition = positionWithinBranch(groupIndex, sourceTable);
if (sourceRowPosition.equals(GroupIndexPosition.BELOW_SEGMENT)) {
return; // nothing to do
}
if(lockTypes != null) {
for(RowType type : lockTypes) {
Row subRow = row.subRow(type);
if(subRow != null) {
store.lock(session, subRow);
}
}
}
int firstSpatialColumn = groupIndex.isSpatial() ? groupIndex.firstSpatialArgument() : -1;
SDType storeData = store.createStoreData(session, groupIndex);
try {
store.resetForWrite(storeData, groupIndex, indexRow);
IndexRowComposition irc = groupIndex.indexRowComposition();
int nFields = irc.getLength();
int f = 0;
while(f < nFields) {
assert irc.isInRowData(f);
assert ! irc.isInHKey(f);
if(f == firstSpatialColumn) {
copyZValueToIndexRow(groupIndex, row, irc);
f += groupIndex.spatialColumns();
} else {
copyFieldToIndexRow(groupIndex, row, irc.getFieldPosition(f++));
}
}
// Non-null values only required for UNIQUE indexes, which GIs cannot be
assert !groupIndex.isUnique() : "Unexpected unique index: " + groupIndex;
indexRow.close(null, false);
indexRow.tableBitmap(tableBitmap(groupIndex, row));
switch (action) {
case CASCADE_STORE:
case STORE:
store.store(session, storeData);
break;
case CASCADE:
case DELETE:
store.clear(session, storeData);
break;
default:
throw new UnsupportedOperationException(action.name());
}
} finally {
store.releaseStoreData(session, storeData);
}
}
public Table getSourceTable () {
return sourceTable;
}
private void copyFieldToIndexRow(GroupIndex groupIndex, Row row, int flattenedIndex) {
Column column = groupIndex.getColumnForFlattenedRow(flattenedIndex);
assert row.rowType().nFields() >= flattenedIndex : "Row: " + row.rowType().toString() + " : does not hold all fields for group index " + flattenedIndex;
indexRow.append(row.value(flattenedIndex), column.getType());
}
private void copyZValueToIndexRow(GroupIndex groupIndex, Row row, IndexRowComposition irc) {
BigDecimal[] coords = new BigDecimal[Spatial.LAT_LON_DIMENSIONS];
Space space = groupIndex.space();
int firstSpatialColumn = groupIndex.firstSpatialArgument();
boolean zNull = false;
for(int d = 0; d < Spatial.LAT_LON_DIMENSIONS; d++) {
if(!zNull) {
ValueSource columnValue = row.value(irc.getFieldPosition(firstSpatialColumn + d));
if (columnValue.isNull()) {
zNull = true;
} else {
coords[d] = ((BigDecimalWrapper)columnValue.getObject()).asBigDecimal();
}
}
}
if (zNull) {
zSource_t3.putNull();
indexRow.append(zSource_t3, NON_NULL_Z_TYPE);
} else {
zSource_t3.putInt64(Spatial.shuffle(space, coords[0].doubleValue(), coords[1].doubleValue()));
indexRow.append(zSource_t3, NON_NULL_Z_TYPE);
}
}
// The group index row's value contains a bitmap indicating which of the tables covered by the index
// have rows contributing to this index row. The leafmost table of the index is represented by bit
// position 0.
private long tableBitmap(GroupIndex groupIndex, Row row) {
long result = 0;
Table table = groupIndex.leafMostTable();
Table end = groupIndex.rootMostTable().getParentTable();
while(table != null && !table.equals(end)) {
if(row.containsRealRowOf(table)) {
result |= (1 << table.getDepth());
}
table = table.getParentTable();
}
return result;
}
private GroupIndexPosition positionWithinBranch(GroupIndex groupIndex, Table table) {
final Table leafMost = groupIndex.leafMostTable();
if(table == null) {
return GroupIndexPosition.ABOVE_SEGMENT;
}
if(table.equals(leafMost)) {
return GroupIndexPosition.WITHIN_SEGMENT;
}
if(table.isDescendantOf(leafMost)) {
return GroupIndexPosition.BELOW_SEGMENT;
}
if(groupIndex.rootMostTable().equals(table)) {
return GroupIndexPosition.WITHIN_SEGMENT;
}
return groupIndex.rootMostTable().isDescendantOf(table)
? GroupIndexPosition.ABOVE_SEGMENT
: GroupIndexPosition.WITHIN_SEGMENT;
}
}