linkStatement.setString(4, tableName);
linkStatement.setByte(5, LinkType.INDEX_TABLE.getSerializedValue());
linkStatement.execute();
}
PrimaryKeyConstraint pkConstraint = statement.getPrimaryKeyConstraint();
String pkName = null;
List<Pair<ColumnName,SortOrder>> pkColumnsNames = Collections.<Pair<ColumnName,SortOrder>>emptyList();
Iterator<Pair<ColumnName,SortOrder>> pkColumnsIterator = Iterators.emptyIterator();
if (pkConstraint != null) {
pkColumnsNames = pkConstraint.getColumnNames();
pkColumnsIterator = pkColumnsNames.iterator();
pkName = pkConstraint.getName();
}
Map<String,Object> tableProps = Maps.newHashMapWithExpectedSize(statement.getProps().size());
Map<String,Object> commonFamilyProps = Collections.emptyMap();
// Somewhat hacky way of determining if property is for HColumnDescriptor or HTableDescriptor
HColumnDescriptor defaultDescriptor = new HColumnDescriptor(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES);
if (!statement.getProps().isEmpty()) {
commonFamilyProps = Maps.newHashMapWithExpectedSize(statement.getProps().size());
Collection<Pair<String,Object>> props = statement.getProps().get(QueryConstants.ALL_FAMILY_PROPERTIES_KEY);
for (Pair<String,Object> prop : props) {
if (defaultDescriptor.getValue(prop.getFirst()) == null) {
tableProps.put(prop.getFirst(), prop.getSecond());
} else {
commonFamilyProps.put(prop.getFirst(), prop.getSecond());
}
}
}
// Although unusual, it's possible to set a mapped VIEW as having immutable rows.
// This tells Phoenix that you're managing the index maintenance yourself.
if (tableType != PTableType.INDEX && (tableType != PTableType.VIEW || viewType == ViewType.MAPPED)) {
Boolean isImmutableRowsProp = (Boolean) tableProps.remove(PTable.IS_IMMUTABLE_ROWS_PROP_NAME);
if (isImmutableRowsProp == null) {
isImmutableRows = connection.getQueryServices().getProps().getBoolean(QueryServices.IMMUTABLE_ROWS_ATTRIB, QueryServicesOptions.DEFAULT_IMMUTABLE_ROWS);
} else {
isImmutableRows = isImmutableRowsProp;
}
}
// Can't set any of these on views or shared indexes on views
if (tableType != PTableType.VIEW && indexId == null) {
saltBucketNum = (Integer) tableProps.remove(PhoenixDatabaseMetaData.SALT_BUCKETS);
if (saltBucketNum != null) {
if (saltBucketNum < 0 || saltBucketNum > SaltingUtil.MAX_BUCKET_NUM) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_BUCKET_NUM).build().buildException();
}
}
// Salt the index table if the data table is salted
if (saltBucketNum == null) {
if (parent != null) {
saltBucketNum = parent.getBucketNum();
}
} else if (saltBucketNum.intValue() == 0) {
saltBucketNum = null; // Provides a way for an index to not be salted if its data table is salted
}
addSaltColumn = (saltBucketNum != null);
}
boolean removedProp = false;
// Can't set MULTI_TENANT or DEFAULT_COLUMN_FAMILY_NAME on an index
if (tableType != PTableType.INDEX && (tableType != PTableType.VIEW || viewType == ViewType.MAPPED)) {
Boolean multiTenantProp = (Boolean) tableProps.remove(PhoenixDatabaseMetaData.MULTI_TENANT);
multiTenant = Boolean.TRUE.equals(multiTenantProp);
// Remove, but add back after our check below
defaultFamilyName = (String)tableProps.remove(PhoenixDatabaseMetaData.DEFAULT_COLUMN_FAMILY_NAME);
removedProp = (defaultFamilyName != null);
}
boolean disableWAL = false;
Boolean disableWALProp = (Boolean) tableProps.remove(PhoenixDatabaseMetaData.DISABLE_WAL);
if (disableWALProp == null) {
disableWAL = isParentImmutableRows; // By default, disable WAL for immutable indexes
} else {
disableWAL = disableWALProp;
}
// Delay this check as it is supported to have IMMUTABLE_ROWS and SALT_BUCKETS defined on views
if ((statement.getTableType() == PTableType.VIEW || indexId != null) && !tableProps.isEmpty()) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.VIEW_WITH_PROPERTIES).build().buildException();
}
if (removedProp) {
tableProps.put(PhoenixDatabaseMetaData.DEFAULT_COLUMN_FAMILY_NAME, defaultFamilyName);
}
List<ColumnDef> colDefs = statement.getColumnDefs();
List<PColumn> columns;
LinkedHashSet<PColumn> pkColumns;
if (tenantId != null && (tableType != PTableType.VIEW && indexId == null)) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_CREATE_TENANT_SPECIFIC_TABLE)
.setSchemaName(schemaName).setTableName(tableName).build().buildException();
}
if (tableType == PTableType.VIEW) {
physicalNames = Collections.singletonList(PNameFactory.newName(parent.getPhysicalName().getString()));
if (viewType == ViewType.MAPPED) {
columns = newArrayListWithExpectedSize(colDefs.size());
pkColumns = newLinkedHashSetWithExpectedSize(colDefs.size());
} else {
// Propagate property values to VIEW.
// TODO: formalize the known set of these properties
multiTenant = parent.isMultiTenant();
saltBucketNum = parent.getBucketNum();
isImmutableRows = parent.isImmutableRows();
disableWAL = (disableWALProp == null ? parent.isWALDisabled() : disableWALProp);
defaultFamilyName = parent.getDefaultFamilyName() == null ? null : parent.getDefaultFamilyName().getString();
List<PColumn> allColumns = parent.getColumns();
if (saltBucketNum != null) { // Don't include salt column in columns, as it should not have it when created
allColumns = allColumns.subList(1, allColumns.size());
}
columns = newArrayListWithExpectedSize(allColumns.size() + colDefs.size());
columns.addAll(allColumns);
pkColumns = newLinkedHashSet(parent.getPKColumns());
}
} else {
columns = newArrayListWithExpectedSize(colDefs.size());
pkColumns = newLinkedHashSetWithExpectedSize(colDefs.size() + 1); // in case salted
}
// Don't add link for mapped view, as it just points back to itself and causes the drop to
// fail because it looks like there's always a view associated with it.
if (!physicalNames.isEmpty()) {
// Upsert physical name for mapped view only if the full physical table name is different than the full table name
// Otherwise, we end up with a self-referencing link and then cannot ever drop the view.
if (viewType != ViewType.MAPPED
|| !physicalNames.get(0).getString().equals(SchemaUtil.getTableName(schemaName, tableName))) {
// Add row linking from data table row to physical table row
PreparedStatement linkStatement = connection.prepareStatement(CREATE_LINK);
for (PName physicalName : physicalNames) {
linkStatement.setString(1, connection.getTenantId() == null ? null : connection.getTenantId().getString());
linkStatement.setString(2, schemaName);
linkStatement.setString(3, tableName);
linkStatement.setString(4, physicalName.getString());
linkStatement.setByte(5, LinkType.PHYSICAL_TABLE.getSerializedValue());
linkStatement.execute();
}
}
}
PreparedStatement colUpsert = connection.prepareStatement(INSERT_COLUMN);
Map<String, PName> familyNames = Maps.newLinkedHashMap();
boolean isPK = false;
int positionOffset = columns.size();
if (saltBucketNum != null) {
positionOffset++;
if (addSaltColumn) {
pkColumns.add(SaltingUtil.SALTING_COLUMN);
}
}
int position = positionOffset;
for (ColumnDef colDef : colDefs) {
if (colDef.isPK()) {
if (isPK) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.PRIMARY_KEY_ALREADY_EXISTS)
.setColumnName(colDef.getColumnDefName().getColumnName()).build().buildException();
}
isPK = true;
}
// do not allow setting NOT-NULL constraint on non-primary columns.
if (!isPK && pkConstraint != null && !pkConstraint.contains(colDef.getColumnDefName())) {
if(Boolean.FALSE.equals(colDef.isNull())) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_NOT_NULL_CONSTRAINT)
.setColumnName(colDef.getColumnDefName().getColumnName()).build().buildException();
}
}