/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.translator.accumulo;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.MutationsRejectedException;
import org.apache.accumulo.core.client.TableExistsException;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.client.admin.TimeType;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Value;
import org.apache.hadoop.io.Text;
import org.teiid.core.types.DataTypeManager;
import org.teiid.language.ColumnReference;
import org.teiid.language.Command;
import org.teiid.language.Delete;
import org.teiid.language.Expression;
import org.teiid.language.ExpressionValueSource;
import org.teiid.language.Insert;
import org.teiid.language.Literal;
import org.teiid.language.SetClause;
import org.teiid.language.Update;
import org.teiid.language.visitor.SQLStringVisitor;
import org.teiid.metadata.Column;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Table;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.UpdateExecution;
public class AccumuloUpdateExecution implements UpdateExecution {
private Command command;
private AccumuloConnection connection;
private AccumuloExecutionFactory aef;
private int updateCount = 0;
private ExecutionContext executionContext;
public AccumuloUpdateExecution(AccumuloExecutionFactory aef, Command command,
ExecutionContext executionContext, RuntimeMetadata metadata,
AccumuloConnection connection) {
this.aef = aef;
this.command = command;
this.connection = connection;
this.executionContext = executionContext;
}
@Override
public void execute() throws TranslatorException {
try {
if (this.command instanceof Insert) {
Insert insert = (Insert)this.command;
performInsert(insert);
}
else if (this.command instanceof Update) {
Update update = (Update)this.command;
performUpdate(update);
}
else if (this.command instanceof Delete) {
Delete delete = (Delete)this.command;
performDelete(delete);
}
} catch (MutationsRejectedException e) {
throw new TranslatorException(e);
} catch (TableNotFoundException e) {
throw new TranslatorException(e);
}
}
private void performInsert(Insert insert) throws TranslatorException, TableNotFoundException, MutationsRejectedException {
Table table = insert.getTable().getMetadataObject();
this.updateCount = 0;
Connector connector = this.connection.getInstance();
BatchWriter writer = createBatchWriter(table, connector);
List<ColumnReference> columns = insert.getColumns();
if (insert.getParameterValues() == null) {
List<Expression> values = ((ExpressionValueSource)insert.getValueSource()).getValues();
writeMutation(writer, columns, values);
this.updateCount++;
}
else {
int batchSize = this.executionContext.getBatchSize();
// bulk insert; should help
Iterator<? extends List<Expression>> args = (Iterator<? extends List<Expression>>) insert.getParameterValues();
while (args.hasNext()) {
List<Expression> values = args.next();
writeMutation(writer, columns, values);
this.updateCount++;
if ((this.updateCount%batchSize) == 0) {
writer.close();
writer = createBatchWriter(table, connector);
}
}
}
// write the mutation
writer.close();
}
private void writeMutation(BatchWriter writer, List<ColumnReference> columns, List<Expression> values)
throws MutationsRejectedException, TranslatorException {
byte[] rowId = getRowId(columns, values);
Mutation mutation = new Mutation(rowId);
for (int i = 0; i < columns.size(); i++) {
Column column = columns.get(i).getMetadataObject();
if (SQLStringVisitor.getRecordName(column).equalsIgnoreCase(AccumuloMetadataProcessor.ROWID)) {
continue;
}
Object value = values.get(i);
if (value instanceof Literal) {
buildMutation(mutation, column, ((Literal)value).getValue());
}
else {
buildMutation(mutation, column, value);
}
}
writer.addMutation(mutation);
}
private void performUpdate(Update update) throws TranslatorException,
TableNotFoundException, MutationsRejectedException {
Table table = update.getTable().getMetadataObject();
if (update.getParameterValues() != null) {
throw new TranslatorException(AccumuloPlugin.Event.TEIID19005,
AccumuloPlugin.Util.gs(AccumuloPlugin.Event.TEIID19005));
}
AccumuloQueryVisitor visitor = new AccumuloQueryVisitor(this.aef);
visitor.visitNode(update.getWhere());
if (!visitor.exceptions.isEmpty()) {
throw visitor.exceptions.get(0);
}
Connector connector = this.connection.getInstance();
BatchWriter writer = createBatchWriter(table, connector);
Text prevRow = null;
Iterator<Entry<Key, Value>> results = AccumuloQueryExecution.runQuery(
this.aef, this.connection.getInstance(),
this.connection.getAuthorizations(), visitor.getRanges(),
table, visitor.scanIterators());
while (results.hasNext()) {
Key key = results.next().getKey();
Text rowId = key.getRow();
if (prevRow == null || !prevRow.equals(rowId)) {
prevRow = rowId;
Mutation mutation = new Mutation(rowId);
List<SetClause> changes = update.getChanges();
for (SetClause clause:changes) {
Column column = clause.getSymbol().getMetadataObject();
if (SQLStringVisitor.getRecordName(column).equalsIgnoreCase(AccumuloMetadataProcessor.ROWID)) {
throw new TranslatorException(
AccumuloPlugin.Event.TEIID19002,
AccumuloPlugin.Util.gs(AccumuloPlugin.Event.TEIID19002,table.getName()));
}
Expression value = clause.getValue();
if (value instanceof Literal) {
buildMutation(mutation, column, ((Literal)value).getValue());
}
else {
throw new TranslatorException(
AccumuloPlugin.Event.TEIID19001,
AccumuloPlugin.Util.gs(AccumuloPlugin.Event.TEIID19001));
}
}
writer.addMutation(mutation);
this.updateCount++;
}
}
writer.close();
}
private BatchWriter createBatchWriter(Table table, Connector connector) throws TranslatorException, TableNotFoundException {
String tableName = SQLStringVisitor.getRecordName(table);
BatchWriter writer;
try {
writer = connector.createBatchWriter(tableName, new BatchWriterConfig());
} catch (TableNotFoundException e) {
try {
connector.tableOperations().create(tableName, true, TimeType.LOGICAL);
} catch (AccumuloException e1) {
throw new TranslatorException(e1);
} catch (AccumuloSecurityException e1) {
throw new TranslatorException(e1);
} catch (TableExistsException e1) {
throw new TranslatorException(e1);
}
writer = connector.createBatchWriter(tableName, new BatchWriterConfig());
}
return writer;
}
private void performDelete(Delete delete) throws TableNotFoundException, MutationsRejectedException, TranslatorException {
if (delete.getParameterValues() != null) {
throw new TranslatorException(AccumuloPlugin.Event.TEIID19005, AccumuloPlugin.Util.gs(AccumuloPlugin.Event.TEIID19005));
}
Table table = delete.getTable().getMetadataObject();
AccumuloQueryVisitor visitor = new AccumuloQueryVisitor(this.aef);
visitor.visitNode(delete.getWhere());
if (!visitor.exceptions.isEmpty()) {
throw visitor.exceptions.get(0);
}
/*
// To get the update count I am taking longer route..
Connector connector = this.connection.getInstance();
BatchDeleter deleter = connector.createBatchDeleter(SQLStringVisitor.getRecordName(table), auths, this.aef.getQueryThreadsCount(), new BatchWriterConfig());
deleter.setRanges(visitor.getRanges());
deleter.delete();
deleter.close();
*/
Text prevRow = null;
Connector connector = this.connection.getInstance();
BatchWriter writer = createBatchWriter(table, connector);
Iterator<Entry<Key,Value>> results = AccumuloQueryExecution.runQuery(this.aef, this.connection.getInstance(), this.connection.getAuthorizations(), visitor.getRanges(), table, null);
while (results.hasNext()) {
Key key = results.next().getKey();
Text rowId = key.getRow();
if (prevRow == null || !prevRow.equals(rowId)) {
this.updateCount++;
}
prevRow = rowId;
Mutation mutation = new Mutation(rowId);
mutation.putDelete(key.getColumnFamily(), key.getColumnQualifier());
writer.addMutation(mutation);
}
writer.close();
}
private byte[] getRowId(List<ColumnReference> columns, List<Expression> values) throws TranslatorException{
for (int i = 0; i < columns.size(); i++) {
Column column = columns.get(i).getMetadataObject();
String rowId = SQLStringVisitor.getRecordName(column);
if (rowId.equalsIgnoreCase(AccumuloMetadataProcessor.ROWID) || AccumuloQueryVisitor.isPartOfPrimaryKey(column)) {
Object value = values.get(i);
if (value instanceof Literal) {
return AccumuloDataTypeManager.serialize(((Literal)value).getValue());
}
throw new TranslatorException(AccumuloPlugin.Event.TEIID19006,
AccumuloPlugin.Util.gs(AccumuloPlugin.Event.TEIID19006));
}
}
return null;
}
private void buildMutation(Mutation mutation, Column column, Object value) {
String CF = column.getProperty(AccumuloMetadataProcessor.CF, false);
String CQ = column.getProperty(AccumuloMetadataProcessor.CQ, false);
String valuePattern = column.getProperty(AccumuloMetadataProcessor.VALUE_IN, false);
if (valuePattern == null) {
valuePattern = AccumuloMetadataProcessor.DEFAULT_VALUE_PATTERN;
}
byte[] columnFamily = CF.getBytes();
byte[] columnQualifier = (CQ == null)?AccumuloDataTypeManager.EMPTY_BYTES:CQ.getBytes();
byte[] columnValue = AccumuloDataTypeManager.EMPTY_BYTES;
valuePattern = valuePattern.substring(1, valuePattern.length()-1); // remove the curleys
if (valuePattern.equals(AccumuloMetadataProcessor.ValueIn.VALUE.name())) {
columnValue = AccumuloDataTypeManager.serialize(value);
}
else if (valuePattern.equals(AccumuloMetadataProcessor.ValueIn.CQ.name())) {
columnQualifier = AccumuloDataTypeManager.serialize(value);
}
mutation.put(columnFamily, columnQualifier, columnValue);
}
@Override
public int[] getUpdateCounts() throws DataNotAvailableException, TranslatorException {
return new int[] {this.updateCount};
}
@Override
public void close() {
}
@Override
public void cancel() throws TranslatorException {
}
}