public MutationPlan compile(UpsertStatement upsert) throws SQLException {
final PhoenixConnection connection = statement.getConnection();
ConnectionQueryServices services = connection.getQueryServices();
final int maxSize = services.getProps().getInt(QueryServices.MAX_MUTATION_SIZE_ATTRIB,QueryServicesOptions.DEFAULT_MAX_MUTATION_SIZE);
final ColumnResolver resolver = FromCompiler.getResolver(upsert, connection);
final TableRef tableRef = resolver.getTables().get(0);
PTable table = tableRef.getTable();
if (table.getType() == PTableType.VIEW) {
if (table.getViewType().isReadOnly()) {
throw new ReadOnlyTableException(table.getSchemaName().getString(),table.getTableName().getString());
}
}
boolean isSalted = table.getBucketNum() != null;
boolean isTenantSpecific = table.isMultiTenant() && connection.getTenantId() != null;
String tenantId = isTenantSpecific ? connection.getTenantId().getString() : null;
int posOffset = isSalted ? 1 : 0;
// Setup array of column indexes parallel to values that are going to be set
List<ColumnName> columnNodes = upsert.getColumns();
List<PColumn> allColumns = table.getColumns();
Map<ColumnRef, byte[]> addViewColumns = Collections.emptyMap();
Map<PColumn, byte[]> overlapViewColumns = Collections.emptyMap();
int[] columnIndexesToBe;
int nColumnsToSet = 0;
int[] pkSlotIndexesToBe;
List<PColumn> targetColumns;
if (table.getViewType() == ViewType.UPDATABLE) {
StatementContext context = new StatementContext(statement, resolver, this.statement.getParameters(), new Scan());
ViewValuesMapBuilder builder = new ViewValuesMapBuilder(context);
ParseNode viewNode = SQLParser.parseCondition(table.getViewExpression());
viewNode.accept(builder);
addViewColumns = builder.getViewColumns();
}
// Allow full row upsert if no columns or only dynamic ones are specified and values count match
if (columnNodes.isEmpty() || columnNodes.size() == upsert.getTable().getDynamicColumns().size()) {
nColumnsToSet = allColumns.size() - posOffset;
columnIndexesToBe = new int[nColumnsToSet];
pkSlotIndexesToBe = new int[columnIndexesToBe.length];
targetColumns = Lists.newArrayListWithExpectedSize(columnIndexesToBe.length);
targetColumns.addAll(Collections.<PColumn>nCopies(columnIndexesToBe.length, null));
for (int i = posOffset, j = posOffset; i < allColumns.size(); i++) {
PColumn column = allColumns.get(i);
columnIndexesToBe[i-posOffset] = i;
targetColumns.set(i-posOffset, column);
if (SchemaUtil.isPKColumn(column)) {
pkSlotIndexesToBe[i-posOffset] = j++;
}
}
if (!addViewColumns.isEmpty()) {
// All view columns overlap in this case
overlapViewColumns = Maps.newHashMapWithExpectedSize(addViewColumns.size());
for (Map.Entry<ColumnRef, byte[]> entry : addViewColumns.entrySet()) {
ColumnRef ref = entry.getKey();
PColumn column = ref.getColumn();
overlapViewColumns.put(column, entry.getValue());
}
addViewColumns.clear();
}
} else {
// Size for worse case
int numColsInUpsert = columnNodes.size();
nColumnsToSet = numColsInUpsert + addViewColumns.size() + (isTenantSpecific ? 1 : 0);
columnIndexesToBe = new int[nColumnsToSet];
pkSlotIndexesToBe = new int[columnIndexesToBe.length];
targetColumns = Lists.newArrayListWithExpectedSize(columnIndexesToBe.length);
targetColumns.addAll(Collections.<PColumn>nCopies(columnIndexesToBe.length, null));
Arrays.fill(columnIndexesToBe, -1); // TODO: necessary? So we'll get an AIOB exception if it's not replaced
Arrays.fill(pkSlotIndexesToBe, -1); // TODO: necessary? So we'll get an AIOB exception if it's not replaced
BitSet pkColumnsSet = new BitSet(table.getPKColumns().size());
int i = 0;
for (i = 0; i < numColsInUpsert; i++) {
ColumnName colName = columnNodes.get(i);
ColumnRef ref = resolver.resolveColumn(null, colName.getFamilyName(), colName.getColumnName());
PColumn column = ref.getColumn();
byte[] viewValue = addViewColumns.remove(ref);
if (viewValue != null) {
if (overlapViewColumns.isEmpty()) {
overlapViewColumns = Maps.newHashMapWithExpectedSize(addViewColumns.size());
}
nColumnsToSet--;
overlapViewColumns.put(column, viewValue);
}
columnIndexesToBe[i] = ref.getColumnPosition();
targetColumns.set(i, column);
if (SchemaUtil.isPKColumn(column)) {
pkColumnsSet.set(pkSlotIndexesToBe[i] = ref.getPKSlotPosition());
}
}
for (Map.Entry<ColumnRef, byte[]> entry : addViewColumns.entrySet()) {
ColumnRef ref = entry.getKey();
PColumn column = ref.getColumn();
columnIndexesToBe[i] = ref.getColumnPosition();
targetColumns.set(i, column);
if (SchemaUtil.isPKColumn(column)) {
pkColumnsSet.set(pkSlotIndexesToBe[i] = ref.getPKSlotPosition());
}
i++;
}
// Add tenant column directly, as we don't want to resolve it as this will fail
if (isTenantSpecific) {
PColumn tenantColumn = table.getPKColumns().get(posOffset);
columnIndexesToBe[i] = tenantColumn.getPosition();
pkColumnsSet.set(pkSlotIndexesToBe[i] = posOffset);
targetColumns.set(i, tenantColumn);
i++;
}
i = posOffset;
for ( ; i < table.getPKColumns().size(); i++) {
PColumn pkCol = table.getPKColumns().get(i);
if (!pkColumnsSet.get(i)) {
if (!pkCol.isNullable()) {
throw new ConstraintViolationException(table.getName().getString() + "." + pkCol.getName().getString() + " may not be null");
}
}
}
}
List<ParseNode> valueNodes = upsert.getValues();
QueryPlan plan = null;
RowProjector rowProjectorToBe = null;
int nValuesToSet;
boolean sameTable = false;
boolean runOnServer = false;
UpsertingParallelIteratorFactory upsertParallelIteratorFactoryToBe = null;
final boolean isAutoCommit = connection.getAutoCommit();
if (valueNodes == null) {
SelectStatement select = upsert.getSelect();
assert(select != null);
select = addTenantAndViewConstants(table, select, tenantId, addViewColumns);
TableRef selectTableRef = FromCompiler.getResolver(select, connection).getTables().get(0);
sameTable = tableRef.equals(selectTableRef);
/* We can run the upsert in a coprocessor if:
* 1) the into table matches from table
* 2) the select query isn't doing aggregation
* 3) autoCommit is on