in calcite-adapter/src/main/java/software/amazon/documentdb/jdbc/metadata/DocumentDbTableSchemaGenerator.java [267:433]
private static void processArray(
final BsonArray array,
final Map<String, DocumentDbSchemaTable> tableMap,
final List<DocumentDbMetadataColumn> foreignKeys,
final String path,
final int arrayLevel,
final String collectionName,
final Map<String, String> tableNameMap) {
// Need to preserve order of fields.
final LinkedHashMap<String, DocumentDbSchemaColumn> columnMap = new LinkedHashMap<>();
int primaryKeyColumn = KEY_COLUMN_NONE;
int level = arrayLevel;
DocumentDbMetadataColumn metadataColumn;
JdbcType prevSqlType = JdbcType.NULL;
JdbcType sqlType;
final String tableName = toName(combinePath(collectionName, path), tableNameMap);
if (tableMap.containsKey(tableName)) {
// If we've already visited this document/table,
// start with the previously discovered columns.
// This will have included and primary/foreign key definitions.
columnMap.putAll(tableMap.get(tableName).getColumnMap());
final String valueColumnPath = VALUE_COLUMN_NAME;
// TODO: Figure out if previous type was array of array.
if (columnMap.containsKey(toName(valueColumnPath, tableNameMap))) {
prevSqlType = columnMap.get(toName(valueColumnPath, tableNameMap)).getSqlType();
} else {
prevSqlType = JdbcType.JAVA_OBJECT;
}
}
// Find the promoted SQL data type for all elements.
sqlType = prevSqlType;
for (BsonValue element : array) {
sqlType = getPromotedSqlType(element.getBsonType(), sqlType);
if (LOGGER.isDebugEnabled()) {
final JdbcType currentSqlType = getPromotedSqlType(element.getBsonType(), JdbcType.NULL);
if (!prevSqlType.equals(currentSqlType)) {
LOGGER.debug(String.format("Type conflict in array table %s, types %s and %s mapped to %s.",
tableName, prevSqlType.name(), currentSqlType, sqlType.name()));
}
}
}
if (!isComplexType(sqlType)) {
if (isComplexType(prevSqlType)) {
// If promoted to scalar type from complex type, remove previous definition.
handleComplexScalarConflict(tableMap, tableName, columnMap);
} else {
// Check to see if we're processing scalars at a different level than previously
// detected.
sqlType = handleArrayLevelConflict(columnMap, level, sqlType);
}
} else if (isComplexType(sqlType) && !isComplexType(prevSqlType)) {
// Promoted from NULL to ARRAY or OBJECT.
handleComplexScalarConflict(tableMap, tableName, columnMap);
}
if (!tableMap.containsKey(path)) {
// Add foreign keys.
//
// Foreign key(s) are the primary key(s) passed from the parent table.
// Minimally, this is the primary key for the "_id" field.
//
// If called from an array parent, it will also include the "index_lvl_<n>"
// column(s) from the previous level in the array.
//
// The primaryKeyColumn and foreignKeyColumn are the one-indexed value
// referencing the order withing the primary or foreign key column.
for (DocumentDbMetadataColumn column : foreignKeys) {
primaryKeyColumn++;
metadataColumn = DocumentDbMetadataColumn
.builder()
.fieldPath(column.getFieldPath())
.sqlName(column.getSqlName())
.sqlType(column.getSqlType())
.dbType(column.getDbType())
.isIndex(column.isIndex())
.isPrimaryKey(primaryKeyColumn != 0)
.foreignKeyTableName(column.getTableName().equals(tableName)
? null
: column.getTableName())
.index(column.getIndex())
.tableName(tableName)
.primaryKeyIndex(primaryKeyColumn)
.foreignKeyIndex(column.getTableName().equals(tableName)
? KEY_COLUMN_NONE
: primaryKeyColumn)
.virtualTableName(column.getVirtualTableName())
.arrayIndexLevel(column.getArrayIndexLevel())
.isGenerated(column.isGenerated())
.build();
metadataColumn.setForeignKeyColumnName(metadataColumn.getForeignKeyTableName() != null
? column.getSqlName()
: null);
columnMap.put(metadataColumn.getSqlName(), metadataColumn);
}
final Map<String, String> columnNameMap = columnMap.values().stream().collect(
Collectors.toMap(
DocumentDbSchemaColumn::getSqlName,
DocumentDbSchemaColumn::getSqlName));
final String indexColumnName = toName(
combinePath(path, INDEX_COLUMN_NAME_PREFIX + level),
columnNameMap);
final DocumentDbMetadataColumn indexColumn;
if (!columnMap.containsKey(indexColumnName)) {
// Add index column. Although it has no path in the original document, we will
// use the path of the generated index field once the original document is unwound.
primaryKeyColumn++;
indexColumn = DocumentDbMetadataColumn
.builder()
.sqlName(indexColumnName)
.fieldPath(path) // Once unwound, the index will be at root level so path = name.
.sqlType(JdbcType.BIGINT)
.isIndex(true)
.isPrimaryKey(true)
.index(columnMap.size() + 1)
.tableName(tableName)
.primaryKeyIndex(primaryKeyColumn)
.foreignKeyIndex(KEY_COLUMN_NONE)
.arrayIndexLevel(level)
.isGenerated(true)
.build();
columnMap.put(indexColumn.getSqlName(), indexColumn);
} else {
// Cast exception should not occur, because we are always creating DocumentDbMetadataColumn.
indexColumn = (DocumentDbMetadataColumn) columnMap.get(indexColumnName);
}
// Add index column to foreign keys
foreignKeys.add(indexColumn);
}
// Add documents, arrays or just the scalar value.
switch (sqlType) {
case JAVA_OBJECT:
processDocumentsInArray(array,
tableMap,
foreignKeys,
path,
collectionName,
tableNameMap);
break;
case ARRAY:
// This will add another level to the array.
level++;
processArrayInArray(array,
tableMap,
foreignKeys,
path,
collectionName,
level,
tableNameMap);
break;
default:
processValuesInArray(
tableMap,
path,
collectionName,
columnMap,
sqlType,
tableNameMap);
break;
}
}