Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
Expand All @@ -38,6 +39,8 @@
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.coprocessorclient.TableInfo;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.execute.MutationState;
Expand Down Expand Up @@ -83,6 +86,7 @@
import org.apache.phoenix.util.MetaDataUtil;
import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.util.TableViewFinderResult;
import org.apache.phoenix.util.ViewUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -231,9 +235,12 @@ public MutationPlan compile(CreateTableStatement create) throws SQLException {
}
if (viewTypeToBe == ViewType.MAPPED && parentToBe.getPKColumns().isEmpty()) {
validateCreateViewCompilation(connection, parentToBe, columnDefs, pkConstraint);
} else if (where != null && viewTypeToBe == ViewType.UPDATABLE) {
} else if (viewTypeToBe == ViewType.UPDATABLE) {
rowKeyMatcher =
WhereOptimizer.getRowKeyMatcher(context, create.getTableName(), parentToBe, where);
if (where == null && parentToBe.isMultiTenant() && connection.getTenantId() != null) {
validateNoExistingTenantViewWithoutWhere(connection, parentToBe);
}
}
verifyIfAnyParentHasIndexesAndViewExtendsPk(parentToBe, columnDefs, pkConstraint);
}
Expand Down Expand Up @@ -435,6 +442,50 @@ private void verifyIfAnyParentHasIndexesAndViewExtendsPk(PTable parentToBe,
}
}

/**
* For a multi-tenant parent, ensure the current tenant has no existing view without a WHERE
* clause. Two such views would share the same ROW_KEY_MATCHER (the tenant-id bytes), causing a
* conflict in the compaction RowKeyMatcher trie.
*/
private void validateNoExistingTenantViewWithoutWhere(final PhoenixConnection connection,
final PTable parentToBe) throws SQLException {
PName myTenantId = connection.getTenantId();
if (myTenantId == null) {
return;
}
if (connection.getQueryServices() instanceof ConnectionlessQueryServicesImpl) {
return;
}
byte[] myTenantIdBytes = myTenantId.getBytes();
TableViewFinderResult childViews;
try {
childViews = ViewUtil.findChildViews(connection,
parentToBe.getTenantId() == null ? null : parentToBe.getTenantId().getString(),
parentToBe.getSchemaName() == null ? null : parentToBe.getSchemaName().getString(),
parentToBe.getTableName().getString());
} catch (IOException e) {
throw new SQLException(e);
}
for (TableInfo info : childViews.getLinks()) {
if (!Arrays.equals(info.getTenantId(), myTenantIdBytes)) {
continue;
}
String childFullName = SchemaUtil.getTableName(
info.getSchemaName() == null ? null : Bytes.toString(info.getSchemaName()),
Bytes.toString(info.getTableName()));
try {
PTable existing = connection.getTable(childFullName);
String existingViewStmt = existing.getViewStatement();
if (existingViewStmt == null || existingViewStmt.isEmpty()) {
throw new SQLExceptionInfo.Builder(
SQLExceptionCode.TENANT_ALREADY_HAS_VIEW_WITHOUT_WHERE_CLAUSE).build().buildException();
}
} catch (TableNotFoundException e) {
// Orphan child link, ignore.
}
}
}

/**
* Validate View creation compilation. 1. If view creation syntax does not specify primary key,
* the method throws SQLException with PRIMARY_KEY_MISSING code. 2. If parent table does not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,12 +498,28 @@ public static byte[] getRowKeyMatcher(final StatementContext context,
PName tenantId = context.getConnection().getTenantId();
boolean isMultiTenant = tenantId != null && parentTable.isMultiTenant();

byte[] tenantIdBytes = tenantId == null
? ByteUtil.EMPTY_BYTE_ARRAY
: ScanUtil.getTenantIdBytes(schema, isSalted, tenantId, isMultiTenant, false);
// Gracefully handle tenant-id encoding failures (e.g., tenant-id type mismatch)
// so that view creation is not blocked; the view will simply have no ROW_KEY_MATCHER.
byte[] tenantIdBytes;
try {
tenantIdBytes = tenantId == null
? ByteUtil.EMPTY_BYTE_ARRAY
: ScanUtil.getTenantIdBytes(schema, isSalted, tenantId, isMultiTenant, false);
} catch (SQLException e) {
tenantIdBytes = ByteUtil.EMPTY_BYTE_ARRAY;
}
if (tenantIdBytes.length != 0) {
rowKeySlotRangesList.add(Arrays.asList(KeyRange.POINT.apply(tenantIdBytes)));
}
// For tenant views without a WHERE clause, return the tenant-id bytes as the
// ROW_KEY_MATCHER so that CompactionScanner can match rows to this view.
if (viewWhereExpression == null) {
if (rowKeySlotRangesList.isEmpty()) {
return ByteUtil.EMPTY_BYTE_ARRAY;
}
ScanRanges scanRange = ScanRanges.createSingleSpan(schema, rowKeySlotRangesList, null, false);
return scanRange.getScanRange().getLowerRange();
}
KeyExpressionVisitor visitor = new KeyExpressionVisitor(context, parentTable);
KeyExpressionVisitor.KeySlots keySlots = viewWhereExpression.accept(visitor);
if (keySlots == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ public SQLException newException(SQLExceptionInfo info) {
"CDC on this table is either enabled or is in the process of being enabled."),
CANNOT_SET_OR_ALTER_MAX_LOOKBACK_FOR_INDEX(10964, "44A46",
"Cannot set or alter " + PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY + " on an index"),
TENANT_ALREADY_HAS_VIEW_WITHOUT_WHERE_CLAUSE(10965, "44A47",
"A tenant cannot create multiple views without a WHERE clause on the same "
+ "multi-tenant parent table."),

/** Sequence related */
SEQUENCE_ALREADY_EXIST(1200, "42Z00", "Sequence already exists.", new Factory() {
Expand Down
Loading