/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program 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 com.foundationdb.qp.loadableplan.std;
import com.foundationdb.ais.model.Table;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.qp.loadableplan.DirectObjectCursor;
import com.foundationdb.qp.loadableplan.DirectObjectPlan;
import com.foundationdb.qp.loadableplan.LoadableDirectObjectPlan;
import com.foundationdb.qp.operator.BindingNotSetException;
import com.foundationdb.qp.operator.RowCursor;
import com.foundationdb.qp.operator.QueryBindings;
import com.foundationdb.qp.operator.QueryContext;
import com.foundationdb.qp.operator.StoreAdapter;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.error.NoSuchTableException;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.sql.server.ServerTransaction;
import com.foundationdb.util.AkibanAppender;
import com.foundationdb.util.Strings;
import java.sql.Types;
import java.io.IOException;
import java.util.*;
/**
* Output table contents in group order.
*/
public class DumpGroupLoadablePlan extends LoadableDirectObjectPlan
{
@Override
public DirectObjectPlan plan() {
return new DirectObjectPlan() {
@Override
public DirectObjectCursor cursor(QueryContext context, QueryBindings bindings) {
return new DumpGroupDirectObjectCursor(context, bindings);
}
@Override
public TransactionMode getTransactionMode() {
return TransactionMode.READ_ONLY;
}
@Override
public OutputMode getOutputMode() {
// Output as raw text, not rows.
return OutputMode.COPY;
}
};
}
public static final int CHARS_PER_MESSAGE = 1000;
public static final int MESSAGES_PER_FLUSH = 100;
public static class DumpGroupDirectObjectCursor extends DirectObjectCursor {
private final QueryContext context;
private final QueryBindings bindings;
private Table rootTable;
private RowCursor cursor;
private Map<Table,Integer> tableSizes;
private StringBuilder buffer;
private GroupRowFormatter formatter;
private int messagesSent;
public DumpGroupDirectObjectCursor(QueryContext context, QueryBindings bindings) {
this.context = context;
this.bindings = bindings;
}
@Override
public void open() {
String currentSchema = context.getCurrentSchema();
String schemaName, tableName;
ValueSource value = valueNotNull(0);
if (value == null)
schemaName = currentSchema;
else
schemaName = value.getString();
tableName = bindings.getValue(1).getString();
rootTable = context.getAIS().getTable(schemaName, tableName);
if (rootTable == null)
throw new NoSuchTableException(schemaName, tableName);
int commitFrequency;
value = valueNotNull(3);
if (value != null)
commitFrequency = value.getInt32();
else if (context.getTransactionPeriodicallyCommit() != ServerTransaction.PeriodicallyCommit.OFF)
commitFrequency = StoreAdapter.COMMIT_FREQUENCY_PERIODICALLY;
else
commitFrequency = 0;
cursor = context.getStore(rootTable)
.newDumpGroupCursor(rootTable.getGroup(), commitFrequency);
cursor.open();
tableSizes = new HashMap<>();
buffer = new StringBuilder();
int insertMaxRowCount;
value = valueNotNull(2);
if (value == null)
insertMaxRowCount = 1;
else
insertMaxRowCount = value.getInt32();
formatter = new SQLRowFormatter(buffer, currentSchema, insertMaxRowCount);
messagesSent = 0;
}
protected ValueSource valueNotNull(int index) {
try {
ValueSource value = bindings.getValue(index);
if (value.isNull())
return null;
else
return value;
}
catch (BindingNotSetException ex) {
return null;
}
}
@Override
public List<String> next() {
if (messagesSent >= MESSAGES_PER_FLUSH) {
messagesSent = 0;
return Collections.emptyList();
}
while (cursor.isActive()) {
Row row = cursor.next();
if (row == null) {
break;
}
RowType rowType = row.rowType();
Table rowTable = rowType.table();
int size = tableSize(rowTable);
if (size < 0)
continue;
try {
formatter.appendRow(rowType, row, size);
}
catch (IOException ex) {
throw new AkibanInternalException("formatting error", ex);
}
if ((buffer.length() >= CHARS_PER_MESSAGE))
break;
}
formatter.flush();
if (buffer.length() > 0) {
String str = buffer.toString();
buffer.setLength(0);
messagesSent++;
return Collections.singletonList(str);
}
return null;
}
@Override
public void close() {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
private int tableSize(Table table) {
Integer size = tableSizes.get(table);
if (size == null) {
if (table.isDescendantOf(rootTable))
size = table.getColumns().size(); // Not ...IncludingInternal()...
else
size = -1;
tableSizes.put(table, size);
}
return size;
}
}
public static abstract class GroupRowFormatter {
protected StringBuilder buffer;
protected String currentSchema;
protected GroupRowFormatter(StringBuilder buffer, String currentSchema) {
this.buffer = buffer;
this.currentSchema = currentSchema;
}
public abstract void appendRow(RowType rowType, Row row, int ncols) throws IOException;
public void flush() {
}
}
public static class SQLRowFormatter extends GroupRowFormatter {
private Map<Table,String> tableNames = new HashMap<>();
private int maxRowCount;
private AkibanAppender appender;
private RowType lastRowType;
private int rowCount, insertWidth;
SQLRowFormatter(StringBuilder buffer, String currentSchema, int maxRowCount) {
super(buffer, currentSchema);
this.maxRowCount = maxRowCount;
appender = AkibanAppender.of(buffer);
}
@Override
public void appendRow(RowType rowType, Row row, int ncols) throws IOException {
if ((lastRowType == rowType) &&
(rowCount++ < maxRowCount)) {
buffer.append(",\n");
for (int i = 0; i < insertWidth; i++) {
buffer.append(' ');
}
}
else {
flush();
int pos = buffer.length();
buffer.append("INSERT INTO ");
buffer.append(tableName(rowType.table()));
buffer.append(" VALUES");
insertWidth = buffer.length() - pos;
lastRowType = rowType;
rowCount = 1;
}
buffer.append('(');
ncols = Math.min(ncols, rowType.nFields());
for (int i = 0; i < ncols; i++) {
if (i > 0) buffer.append(", ");
rowType.typeAt(i).formatAsLiteral(row.value(i), appender);
}
buffer.append(')');
}
public void flush() {
if (rowCount > 0) {
buffer.append(";\n");
lastRowType = null;
rowCount = 0;
}
}
protected String tableName(Table table) {
String name = tableNames.get(table);
if (name == null) {
TableName tableName = table.getName();
name = Strings.quotedIdent(tableName.getTableName(), '"', true);
if (!tableName.getSchemaName().equals(currentSchema)) {
name = Strings.quotedIdent(tableName.getSchemaName(), '"', true) + "." + name;
}
tableNames.put(table, name);
}
return name;
}
}
@Override
public int[] jdbcTypes() {
return TYPES;
}
private static final int[] TYPES = new int[] { Types.VARCHAR };
}