Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2015-2017 Emmanuel Keller / QWAZR
*
* Licensed 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 com.qwazr.database.store;
import com.qwazr.database.model.ColumnDefinition;
import com.qwazr.database.model.InternalColumnDefinition;
import com.qwazr.database.model.TableDefinition;
import com.qwazr.database.store.CollectorInterface.LongCounter;
import com.qwazr.database.store.keys.ColumnDefKey;
import com.qwazr.database.store.keys.ColumnDefsKey;
import com.qwazr.database.store.keys.ColumnIndexKey;
import com.qwazr.database.store.keys.ColumnIndexesKey;
import com.qwazr.database.store.keys.ColumnStoreKey;
import com.qwazr.database.store.keys.ColumnStoresKey;
import com.qwazr.database.store.keys.PrimaryIdsKey;
import com.qwazr.database.store.keys.PrimaryIndexKey;
import com.qwazr.server.ServerException;
import com.qwazr.utils.LoggerUtils;
import com.qwazr.utils.concurrent.ReadWriteLock;
import org.roaringbitmap.IntIterator;
import org.roaringbitmap.RoaringBitmap;
import javax.ws.rs.core.Response;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
public class Table implements Closeable {
private static final Logger LOGGER = LoggerUtils.getLogger(Table.class);
private final ReadWriteLock rwlColumns = ReadWriteLock.stamped();
private final ExecutorService executorService;
private final File directory;
private final KeyStore keyStore;
private final ColumnDefsKey columnDefsKey;
private final PrimaryIndexKey primaryIndexKey;
Table(final ExecutorService executorService, final File directory, final KeyStore.Impl storeImpl) {
this.executorService = executorService;
this.directory = directory;
LOGGER.info(() -> "Load table: " + directory);
final File dbFile = new File(directory, storeImpl.directoryName);
try {
keyStore = storeImpl.storeClass.getConstructor(File.class).newInstance(dbFile);
} catch (ReflectiveOperationException e) {
throw ServerException.of(e);
}
columnDefsKey = new ColumnDefsKey();
primaryIndexKey = new PrimaryIndexKey();
}
void closeNoLock() throws IOException {
keyStore.close();
}
@Override
public void close() throws IOException {
closeNoLock();
Tables.close(directory);
}
public void delete() throws IOException {
if (keyStore.exists())
keyStore.delete();
}
public KeyStore.Impl getImplementation() {
return keyStore.getImplementation();
}
public Map getColumns() throws IOException {
return rwlColumns.readEx(() -> {
Map map = columnDefsKey.getColumns(keyStore);
Map res = new LinkedHashMap<>();
res.put(TableDefinition.ID_COLUMN_NAME, ColumnDefinition.ID_COLUMN_DEF);
map.forEach((s, columnInternalDefinition) -> res.put(s, new ColumnDefinition(columnInternalDefinition)));
return res;
});
}
private void addColumn(final Map columns, final String columnName,
final ColumnDefinition columnDefinition) throws IOException {
// Find the next available column id
final BitSet bitset = new BitSet();
if (columns != null)
columns.forEach((s, columnInternalDefinition) -> bitset.set(columnInternalDefinition.columnId));
final int columnId = bitset.nextClearBit(0);
// Write the new column
new ColumnDefKey(columnName).setValue(keyStore, new InternalColumnDefinition(columnDefinition, columnId));
}
final public void setColumn(final String columnName, final ColumnDefinition columnDefinition) throws IOException {
rwlColumns.writeEx(() -> {
// Check if the column already exists
final Map columns = columnDefsKey.getColumns(keyStore);
if (columns != null) {
final InternalColumnDefinition oldColDef = columns.get(columnName);
if (oldColDef != null) {
if (oldColDef.mode == columnDefinition.mode && oldColDef.type == columnDefinition.type)
return;
throw new ServerException(Response.Status.NOT_ACCEPTABLE, "The column cannot be changed.");
}
}
addColumn(columns, columnName, columnDefinition);
});
}
final public void removeColumn(final String columnName) throws IOException {
rwlColumns.writeEx(() -> {
// Check if the column exists
final ColumnDefKey columnDefKey = new ColumnDefKey(columnName);
final InternalColumnDefinition colDef = columnDefKey.getValue(keyStore);
if (colDef == null)
throw new ServerException(Response.Status.NOT_ACCEPTABLE,
"Cannot delete an unknown column: " + columnName);
// Delete stored values
new ColumnStoresKey(colDef.columnId).deleteAll(keyStore);
// Delete any index
new ColumnIndexesKey(colDef).deleteAll(keyStore);
// Remove the column definition
columnDefKey.deleteValue(keyStore);
});
}
private void deleteRow(final int docId) throws IOException {
rwlColumns.readEx(() -> {
for (InternalColumnDefinition colDef : columnDefsKey.getColumns(keyStore).values()) {
ColumnStoreKey> columnStoreKey = ColumnStoreKey.newInstance(colDef, docId);
if (colDef.mode == ColumnDefinition.Mode.INDEXED)
new ColumnIndexesKey(colDef).remove(keyStore, columnStoreKey);
columnStoreKey.deleteValue(keyStore);
}
});
}
final public LinkedHashMap getRow(final String key, final Set columnNames)
throws IOException {
if (key == null)
return null;
final Integer docId = new PrimaryIdsKey(key).getValue(keyStore);
if (docId == null)
return null;
return rwlColumns.readEx(() -> {
final Map columns = columnNames == null || columnNames.isEmpty() ?
columnDefsKey.getColumns(keyStore) :
getColumns(columnNames);
return getRowByIdNoLock(docId, columns);
});
}
final public boolean deleteRow(final String key) throws IOException {
if (key == null)
throw new ServerException(Response.Status.NOT_ACCEPTABLE, "Cannot delete an empty key");
final PrimaryIdsKey primaryIdsKey = new PrimaryIdsKey(key);
final Integer docId = primaryIdsKey.getValue(keyStore);
if (docId == null)
return false;
deleteRow(docId);
primaryIndexKey.remove(keyStore, docId);
primaryIdsKey.deleteValue(keyStore);
return true;
}
private boolean upsertRowNoCommit(String key, final Map row) throws IOException {
if (row == null)
return false;
// Check if the primary key is present
if (key == null) {
Object o = row.get(TableDefinition.ID_COLUMN_NAME);
if (o != null)
key = o.toString();
if (key == null)
throw new ServerException(Response.Status.NOT_ACCEPTABLE,
"The primary key is missing (" + TableDefinition.ID_COLUMN_NAME + ")");
}
final PrimaryIdsKey primaryIdsKey = new PrimaryIdsKey(key);
Integer tempId = primaryIdsKey.getValue(keyStore);
final AtomicBoolean newDocIdUsed;
if (tempId == null) {
tempId = primaryIndexKey.nextDocId(keyStore, key);
newDocIdUsed = new AtomicBoolean(false);
} else
newDocIdUsed = null;
final int docId = tempId;
try {
return rwlColumns.readEx(() -> {
final Map columns = columnDefsKey.getColumns(keyStore);
for (Map.Entry entry : row.entrySet()) {
final String colName = entry.getKey();
if (TableDefinition.ID_COLUMN_NAME.equals(colName))
continue;
final InternalColumnDefinition colDef = columns.get(colName);
if (colDef == null)
throw new ServerException(Response.Status.NOT_ACCEPTABLE, "Unknown column: " + colName);
final ColumnStoreKey> columnStoreKey = ColumnStoreKey.newInstance(colDef, docId);
final Object valueObject = entry.getValue();
if (colDef.mode == ColumnDefinition.Mode.INDEXED) {
final ColumnIndexesKey columnIndexesKey = new ColumnIndexesKey(colDef);
// TODO optimization : Check if values are identical, if they are we don't have to update anything
columnIndexesKey.remove(keyStore, columnStoreKey);
columnIndexesKey.select(keyStore, valueObject, docId);
}
columnStoreKey.setObjectValue(keyStore, valueObject);
}
primaryIndexKey.select(keyStore, docId);
if (newDocIdUsed != null)
newDocIdUsed.set(true);
return true;
});
} finally {
if (newDocIdUsed != null && !newDocIdUsed.get())
primaryIndexKey.remove(keyStore, docId);
}
}
final public boolean upsertRow(final String key, final Map row) throws IOException {
return upsertRowNoCommit(key, row);
}
final public int upsertRows(final Collection