/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.rdbms.schema;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
/**
* Manages the rows in a value table. These tables have two columns: an internal
* id column and a value column.
*
* @author James Leigh
*
*/
public class ValueTable {
public static int BATCH_SIZE = 8 * 1024;
public static final long NIL_ID = 0; // TODO
private static final String[] PKEY = { "id" };
private static final String[] VALUE_INDEX = { "value" };
private int length = -1;
private int sqlType;
private int idType;
private String INSERT;
private String INSERT_SELECT;
private String EXPUNGE;
private RdbmsTable table;
private RdbmsTable temporary;
private ValueBatch batch;
private BlockingQueue<Batch> queue;
private boolean indexingValues;
private PreparedStatement insertSelect;
public void setQueue(BlockingQueue<Batch> queue) {
this.queue = queue;
}
public boolean isIndexingValues() {
return indexingValues;
}
public void setIndexingValues(boolean indexingValues) {
this.indexingValues = indexingValues;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getSqlType() {
return sqlType;
}
public void setSqlType(int sqlType) {
this.sqlType = sqlType;
}
public int getIdType() {
return idType;
}
public void setIdType(int sqlType) {
this.idType = sqlType;
}
public RdbmsTable getRdbmsTable() {
return table;
}
public void setRdbmsTable(RdbmsTable table) {
this.table = table;
}
public RdbmsTable getTemporaryTable() {
return temporary;
}
public void setTemporaryTable(RdbmsTable temporary) {
this.temporary = temporary;
}
public String getName() {
return table.getName();
}
public long size() {
return table.size();
}
public int getBatchSize() {
return BATCH_SIZE;
}
public void initialize()
throws SQLException
{
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO ").append(getInsertTable().getName());
sb.append(" (id, value) VALUES (?, ?)");
setINSERT(sb.toString());
sb.delete(0, sb.length());
sb.append("DELETE FROM ").append(table.getName()).append("\n");
sb.append("WHERE 1=1 ");
EXPUNGE = sb.toString();
if (temporary != null) {
sb.delete(0, sb.length());
sb.append("INSERT INTO ").append(table.getName());
sb.append(" (id, value) SELECT DISTINCT id, value FROM ");
sb.append(temporary.getName()).append(" tmp\n");
sb.append("WHERE NOT EXISTS (SELECT id FROM ").append(table.getName());
sb.append(" val WHERE val.id = tmp.id)");
INSERT_SELECT = sb.toString();
}
if (!table.isCreated()) {
createTable(table);
table.primaryIndex(PKEY);
if (isIndexingValues()) {
table.index(sqlType, VALUE_INDEX);
}
}
else {
table.count();
}
if (temporary != null && !temporary.isCreated()) {
createTemporaryTable(temporary);
}
}
public void close()
throws SQLException
{
if (insertSelect != null) {
insertSelect.close();
}
if (temporary != null) {
temporary.close();
}
table.close();
}
public synchronized void insert(Number id, Object value)
throws SQLException, InterruptedException
{
ValueBatch batch = getValueBatch();
if (isExpired(batch)) {
batch = newValueBatch();
initBatch(batch);
}
batch.setObject(1, id);
batch.setObject(2, value);
batch.addBatch();
queue(batch);
}
public ValueBatch getValueBatch() {
return this.batch;
}
public boolean isExpired(ValueBatch batch) {
if (batch == null || batch.isFull())
return true;
return queue == null || !queue.remove(batch);
}
public ValueBatch newValueBatch() {
return new ValueBatch();
}
public void initBatch(ValueBatch batch)
throws SQLException
{
batch.setTable(table);
batch.setBatchStatement(prepareInsert(INSERT));
batch.setMaxBatchSize(getBatchSize());
if (temporary != null) {
batch.setTemporary(temporary);
if (insertSelect == null) {
insertSelect = prepareInsertSelect(INSERT_SELECT);
}
batch.setInsertStatement(insertSelect);
}
}
public void queue(ValueBatch batch)
throws SQLException, InterruptedException
{
this.batch = batch;
if (queue == null) {
batch.flush();
}
else {
queue.put(batch);
}
}
public void optimize()
throws SQLException
{
table.optimize();
}
public boolean expunge(String condition)
throws SQLException
{
synchronized (table) {
int count = table.executeUpdate(EXPUNGE + condition);
if (count < 1)
return false;
table.modified(0, count);
return true;
}
}
public List<Long> maxIds(int shift, int mod)
throws SQLException
{
String column = "id";
StringBuilder expr = new StringBuilder();
expr.append("MOD((").append(column);
expr.append(" >> ").append(shift);
expr.append(") + ").append(mod).append(", ");
expr.append(mod);
expr.append(")");
StringBuilder sb = new StringBuilder();
sb.append("SELECT ").append(expr);
sb.append(", MAX(").append(column);
sb.append(")\nFROM ").append(getName());
sb.append("\nGROUP BY ").append(expr);
String query = sb.toString();
PreparedStatement st = table.prepareStatement(query);
try {
ResultSet rs = st.executeQuery();
try {
List<Long> result = new ArrayList<Long>();
while (rs.next()) {
result.add(rs.getLong(2));
}
return result;
}
finally {
rs.close();
}
}
finally {
st.close();
}
}
public String sql(int type, int length) {
switch (type) {
case Types.VARCHAR:
if (length > 0)
return "VARCHAR(" + length + ")";
return "TEXT";
case Types.LONGVARCHAR:
if (length > 0)
return "LONGVARCHAR(" + length + ")";
return "TEXT";
case Types.BIGINT:
return "BIGINT";
case Types.INTEGER:
return "INTEGER";
case Types.SMALLINT:
return "SMALLINT";
case Types.FLOAT:
return "FLOAT";
case Types.DOUBLE:
return "DOUBLE";
case Types.DECIMAL:
return "DECIMAL";
case Types.BOOLEAN:
return "BOOLEAN";
case Types.TIMESTAMP:
return "TIMESTAMP";
default:
throw new AssertionError("Unsupported SQL Type: " + type);
}
}
@Override
public String toString() {
return getName();
}
protected RdbmsTable getInsertTable() {
RdbmsTable tmp = getTemporaryTable();
if (tmp == null) {
tmp = getRdbmsTable();
}
return tmp;
}
protected PreparedStatement prepareInsert(String sql)
throws SQLException
{
return table.prepareStatement(sql);
}
protected PreparedStatement prepareInsertSelect(String sql)
throws SQLException
{
return table.prepareStatement(sql);
}
protected void createTable(RdbmsTable table)
throws SQLException
{
StringBuilder sb = new StringBuilder();
sb.append(" id ").append(sql(idType, -1)).append(" NOT NULL,\n");
sb.append(" value ").append(sql(sqlType, length));
sb.append(" NOT NULL\n");
table.createTable(sb);
}
protected void createTemporaryTable(RdbmsTable table)
throws SQLException
{
StringBuilder sb = new StringBuilder();
sb.append(" id ").append(sql(idType, -1)).append(" NOT NULL,\n");
sb.append(" value ").append(sql(sqlType, length));
sb.append(" NOT NULL\n");
table.createTemporaryTable(sb);
}
public void setINSERT(String insert) {
this.INSERT = insert;
}
}