/*
* 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.jena.tdb.store.tupletable;
import static java.lang.String.format ;
import java.util.Iterator ;
import org.apache.jena.atlas.lib.Closeable ;
import org.apache.jena.atlas.lib.Sync ;
import org.apache.jena.atlas.lib.tuple.Tuple ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.tdb.TDBException ;
import org.apache.jena.tdb.store.NodeId ;
import org.apache.jena.tdb.sys.SystemTDB ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
/** A TupleTable is a set of TupleIndexes. The first TupleIndex is the "primary" index and must exist */
public class TupleTable implements Sync, Closeable
{
private static Logger log = LoggerFactory.getLogger(TupleTable.class) ;
private final TupleIndex[] indexes ;
private final TupleIndex scanAllIndex ; // Use this index if a complete scan is needed.
private final int tupleLen ;
private boolean syncNeeded = false ;
public TupleTable(int tupleLen, TupleIndex[] indexes)
{
this.tupleLen = tupleLen ;
this.indexes = indexes ;
if ( indexes[0] == null )
throw new TDBException("TupleTable: no primary index") ;
for ( TupleIndex index : indexes )
{
if ( index != null && index.getTupleLength() != tupleLen )
throw new TDBException("Incompatible index: "+index.getMapping()) ;
}
scanAllIndex = chooseScanAllIndex(tupleLen, indexes) ;
}
/** Choose an index to scan in case we are asked for everything
* This needs to be ???G for the distinctAdjacent filter in union query to work.
*/
private static TupleIndex chooseScanAllIndex(int tupleLen, TupleIndex[] indexes)
{
if ( tupleLen != 4 )
return indexes[0] ;
for ( TupleIndex index : indexes )
{
// First look for SPOG
if ( index.getName().equals("SPOG") )
return index ;
}
for ( TupleIndex index : indexes )
{
// Then look for any ???G
if ( index.getName().endsWith("G") )
return index ;
}
Log.warn(SystemTDB.errlog, "Did not find a ???G index for full scans") ;
return indexes[0] ;
}
/** Insert a tuple - return true if it was really added, false if it was a duplicate */
public boolean add(Tuple<NodeId> t)
{
if ( tupleLen != t.len() )
throw new TDBException(format("Mismatch: inserting tuple of length %d into a table of tuples of length %d", t.len(), tupleLen)) ;
for ( int i = 0 ; i < indexes.length ; i++ )
{
if ( indexes[i] == null ) continue ;
if ( ! indexes[i].add(t) )
{
if ( i == 0 )
{
duplicate(t) ;
return false ;
}
unexpectedDuplicate(t, i) ;
throw new TDBException(format("Secondary index duplicate: %s -> %s",indexes[i].getMapping(), t)) ;
}
syncNeeded = true ;
}
return true ;
}
protected void duplicate(Tuple<NodeId> t)
{ }
protected void unexpectedDuplicate(Tuple<NodeId> t, int i)
{
// System.err.printf("Duplicate on secondary index: %s\n",t) ;
// for ( TupleIndex index : indexes )
// {
// if ( index.find(t) != null )
// System.err.printf("%s: Present\n",index.getLabel()) ;
// else
// System.err.printf("%s: Absent\n",index.getLabel()) ;
// }
//
// try {
// OutputStream f = new FileOutputStream("LOG") ;
// IndentedWriter w = new IndentedWriter(f) ;
// ( (BPlusTree) ((TupleIndexRecord)indexes[i]).getRangeIndex() ).dump(w) ;
// w.flush() ;
// f.flush() ;
// f.close() ;
// } catch ( IOException ex ) {}
}
/** Delete a tuple - return true if it was deleted, false if it didn't exist */
public boolean delete( Tuple<NodeId> t )
{
if ( tupleLen != t.len() )
throw new TDBException(format("Mismatch: deleting tuple of length %d from a table of tuples of length %d", t.len(), tupleLen)) ;
boolean rc = false ;
for ( TupleIndex indexe : indexes )
{
if ( indexe == null )
{
continue;
}
// Use return boolean
rc = indexe.delete( t );
if ( rc )
{
syncNeeded = true;
}
}
return rc ;
}
/** Find all matching tuples - a slot of NodeId.NodeIdAny (or null) means match any */
public Iterator<Tuple<NodeId>> find(Tuple<NodeId> pattern)
{
// for ( NodeId n : pattern.tuple() )
// {
// if ( n == null )
// log.warn("find(Tuple<NodeId> pattern): Null found: "+pattern) ;
// }
if ( tupleLen != pattern.len() )
throw new TDBException(format("Mismatch: finding tuple of length %d in a table of tuples of length %d", pattern.len(), tupleLen)) ;
int numSlots = 0 ;
// Canonical form.
for ( int i = 0 ; i < tupleLen ; i++ )
{
NodeId x = pattern.get(i) ;
if ( ! NodeId.isAny(x) )
numSlots++ ;
}
if ( numSlots == 0 )
return scanAllIndex.all() ;
int indexNumSlots = 0 ;
TupleIndex index = null ;
for ( TupleIndex idx : indexes )
{
if ( idx != null )
{
int w = idx.weight( pattern );
if ( w > indexNumSlots )
{
indexNumSlots = w;
index = idx;
}
}
}
if ( index == null )
// No index at all. Scan.
index = indexes[0] ;
return index.find(pattern) ;
}
@Override
final public void close()
{
for ( TupleIndex idx : indexes )
{
if ( idx != null )
idx.close();
}
}
// public void dumpIndexes()
// {
// for ( TupleIndex idx : indexes )
// {
// if ( idx != null )
// ;
// //idx.dump();
// }
//
// }
@Override
public void sync()
{
if ( syncNeeded )
{
for ( TupleIndex idx : indexes )
{
if ( idx != null )
idx.sync() ;
}
syncNeeded = false ;
}
}
public boolean isEmpty() { return indexes[0].isEmpty() ; }
public void clear()
{
for ( TupleIndex idx : indexes )
{
if ( idx != null )
idx.clear() ;
}
syncNeeded = true ;
}
public long size()
{
return indexes[0].size() ;
}
/** Get i'th index */
public TupleIndex getIndex(int i) { return indexes[i] ; }
/** Get all indexes - for code that maipulates internal structures directly - use with care */
public TupleIndex[] getIndexes() { return indexes ; }
/** Get the width of tuples in indexes in this table */
public int getTupleLen() { return tupleLen ; }
/** Set index - for code that maipulates internal structures directly - use with care */
public void setTupleIndex(int i, TupleIndex index)
{
if ( index != null && index.getTupleLength() != tupleLen )
throw new TDBException("Incompatible index: "+index.getMapping()) ;
indexes[i] = index ;
}
/** Number of indexes on this tuple table */
public int numIndexes() { return indexes.length ; }
}