/* * Copyright (c) 2002-2009 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j 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.neo4j.kernel.impl.nioneo.store; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Implementation of the node store. */ public class PropertyIndexStore extends AbstractStore implements Store { // store version, should end with this string (byte encoded) private static final String VERSION = "PropertyIndex v0.9.5"; private static final int KEY_STORE_BLOCK_SIZE = 30; // in_use(byte)+prop_count(int)+key_block_id(int) private static final int RECORD_SIZE = 9; private DynamicStringStore keyPropertyStore; public PropertyIndexStore( String fileName, Map<?,?> config ) { super( fileName, config ); } public PropertyIndexStore( String fileName ) { super( fileName ); } protected void initStorage() { keyPropertyStore = new DynamicStringStore( getStorageFileName() + ".keys", getConfig() ); } public String getTypeAndVersionDescriptor() { return VERSION; } public int getRecordSize() { return RECORD_SIZE; } @Override protected void setRecovered() { super.setRecovered(); keyPropertyStore.setRecovered(); } @Override protected void unsetRecovered() { super.unsetRecovered(); keyPropertyStore.unsetRecovered(); } @Override public void makeStoreOk() { keyPropertyStore.makeStoreOk(); super.makeStoreOk(); } @Override public void rebuildIdGenerators() { keyPropertyStore.rebuildIdGenerators(); super.rebuildIdGenerators(); } public void updateIdGenerators() { keyPropertyStore.updateHighId(); this.updateHighId(); } public void freeBlockId( int id ) { keyPropertyStore.freeBlockId( id ); } @Override protected void closeStorage() { keyPropertyStore.close(); keyPropertyStore = null; } @Override public void flushAll() { keyPropertyStore.flushAll(); super.flushAll(); } public static void createStore( String fileName ) { createEmptyStore( fileName, VERSION ); DynamicStringStore.createStore( fileName + ".keys", KEY_STORE_BLOCK_SIZE ); } public PropertyIndexData[] getPropertyIndexes( int count ) { LinkedList<PropertyIndexData> indexList = new LinkedList<PropertyIndexData>(); long maxIdInUse = getHighestPossibleIdInUse(); int found = 0; for ( int i = 0; i <= maxIdInUse && found < count; i++ ) { PropertyIndexRecord record; try { record = getRecord( i ); } catch ( InvalidRecordException t ) { continue; } found++; indexList.add( new PropertyIndexData( record.getId(), getStringFor( record ) ) ); } return indexList.toArray( new PropertyIndexData[indexList.size()] ); } public PropertyIndexData getPropertyIndex( int id ) { PropertyIndexRecord record = getRecord( id ); return new PropertyIndexData( record.getId(), getStringFor( record ) ); } public PropertyIndexData getPropertyIndex( int id, boolean recovered ) { assert recovered; try { setRecovered(); PropertyIndexRecord record = getRecord( id ); return new PropertyIndexData( record.getId(), getStringFor( record ) ); } finally { unsetRecovered(); } } public PropertyIndexRecord getRecord( int id ) { PropertyIndexRecord record; PersistenceWindow window = acquireWindow( id, OperationType.READ ); try { record = getRecord( id, window ); } finally { releaseWindow( window ); } Collection<DynamicRecord> keyRecords = keyPropertyStore.getLightRecords( record.getKeyBlockId() ); for ( DynamicRecord keyRecord : keyRecords ) { record.addKeyRecord( keyRecord ); } return record; } public PropertyIndexRecord getLightRecord( int id ) { PersistenceWindow window = acquireWindow( id, OperationType.READ ); try { PropertyIndexRecord record = getRecord( id, window ); record.setIsLight( true ); return record; } finally { releaseWindow( window ); } } public void updateRecord( PropertyIndexRecord record, boolean recovered ) { assert recovered; setRecovered(); try { updateRecord( record ); } finally { unsetRecovered(); } } public void updateRecord( PropertyIndexRecord record ) { PersistenceWindow window = acquireWindow( record.getId(), OperationType.WRITE ); try { updateRecord( record, window ); } finally { releaseWindow( window ); } if ( !record.isLight() ) { for ( DynamicRecord keyRecord : record.getKeyRecords() ) { keyPropertyStore.updateRecord( keyRecord ); } } } public Collection<DynamicRecord> allocateKeyRecords( int keyBlockId, char[] chars ) { return keyPropertyStore.allocateRecords( keyBlockId, chars ); } public int nextKeyBlockId() { return keyPropertyStore.nextBlockId(); } private PropertyIndexRecord getRecord( int id, PersistenceWindow window ) { Buffer buffer = window.getOffsettedBuffer( id ); boolean inUse = (buffer.get() == Record.IN_USE.byteValue()); if ( !inUse ) { throw new InvalidRecordException( "Record[" + id + "] not in use" ); } PropertyIndexRecord record = new PropertyIndexRecord( id ); record.setInUse( inUse ); record.setPropertyCount( buffer.getInt() ); record.setKeyBlockId( buffer.getInt() ); return record; } private void updateRecord( PropertyIndexRecord record, PersistenceWindow window ) { int id = record.getId(); Buffer buffer = window.getOffsettedBuffer( id ); if ( record.inUse() ) { buffer.put( Record.IN_USE.byteValue() ).putInt( record.getPropertyCount() ).putInt( record.getKeyBlockId() ); } else { buffer.put( Record.NOT_IN_USE.byteValue() ); if ( !isInRecoveryMode() ) { freeId( id ); } } } public void makeHeavy( PropertyIndexRecord record ) { record.setIsLight( false ); Collection<DynamicRecord> keyRecords = keyPropertyStore.getRecords( record.getKeyBlockId() ); for ( DynamicRecord keyRecord : keyRecords ) { record.addKeyRecord( keyRecord ); } } public String getStringFor( PropertyIndexRecord propRecord ) { int recordToFind = propRecord.getKeyBlockId(); Iterator<DynamicRecord> records = propRecord.getKeyRecords().iterator(); List<char[]> charList = new LinkedList<char[]>(); int totalSize = 0; while ( recordToFind != Record.NO_NEXT_BLOCK.intValue() && records.hasNext() ) { DynamicRecord record = records.next(); if ( record.inUse() && record.getId() == recordToFind ) { if ( record.isLight() ) { keyPropertyStore.makeHeavy( record ); } if ( !record.isCharData() ) { ByteBuffer buf = ByteBuffer.wrap( record.getData() ); char[] chars = new char[record.getData().length / 2]; totalSize += chars.length; buf.asCharBuffer().get( chars ); charList.add( chars ); } else { charList.add( record.getDataAsChar() ); } recordToFind = record.getNextBlock(); // TODO: optimize here, high chance next is right one records = propRecord.getKeyRecords().iterator(); } } StringBuffer buf = new StringBuffer(); for ( char[] str : charList ) { buf.append( str ); } return buf.toString(); } public String toString() { return "PropertyIndexStore"; } @Override protected boolean versionFound( String version ) { if ( !version.startsWith( "PropertyIndex" ) ) { // non clean shutdown, need to do recover with right neo return false; } if ( version.equals( "PropertyIndex v0.9.3" ) ) { rebuildIdGenerator(); closeIdGenerator(); return true; } throw new IllegalStoreVersionException( "Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j." ); } public List<WindowPoolStats> getAllWindowPoolStats() { List<WindowPoolStats> list = new ArrayList<WindowPoolStats>(); list.add( keyPropertyStore.getWindowPoolStats() ); list.add( getWindowPoolStats() ); return list; } }