in modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/DdlSqlToCommandConverter.java [186:368]
private CreateTableCommand convertCreateTable(IgniteSqlCreateTable createTblNode, PlanningContext ctx) {
CreateTableCommand createTblCmd = new CreateTableCommand();
String schemaName = deriveSchemaName(createTblNode.name(), ctx);
String tableName = deriveObjectName(createTblNode.name(), ctx, "tableName");
createTblCmd.schemaName(schemaName);
createTblCmd.tableName(tableName);
createTblCmd.ifNotExists(createTblNode.ifNotExists());
createTblCmd.templateName(QueryUtils.TEMPLATE_PARTITIONED);
if (createTblNode.createOptionList() != null) {
for (SqlNode optNode : createTblNode.createOptionList().getList()) {
IgniteSqlCreateTableOption opt = (IgniteSqlCreateTableOption)optNode;
tblOptionProcessors.getOrDefault(opt.key(), UNSUPPORTED_OPTION_PROCESSOR).process(opt, ctx, createTblCmd);
}
}
IgnitePlanner planner = ctx.planner();
if (createTblNode.query() == null) {
List<SqlColumnDeclaration> colDeclarations = createTblNode.columnList().getList().stream()
.filter(SqlColumnDeclaration.class::isInstance)
.map(SqlColumnDeclaration.class::cast)
.collect(Collectors.toList());
List<ColumnDefinition> cols = new ArrayList<>();
for (SqlColumnDeclaration col : colDeclarations) {
if (!col.name.isSimple())
throw new IgniteSQLException("Unexpected value of columnName [" +
"expected a simple identifier, but was " + col.name + "; " +
"querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
String name = col.name.getSimple();
RelDataType type = planner.convert(col.dataType);
Object dflt = null;
assert col.expression == null || col.expression instanceof SqlLiteral;
if (col.expression != null
&& (((SqlLiteral)col.expression).getTypeName() != SqlTypeName.NULL || type instanceof OtherType)) {
if (type instanceof OtherType)
throw new IgniteSQLException("Type '" + type + "' doesn't support default value.");
Type storageType = ctx.typeFactory().getResultClass(type);
DataContext dataCtx = new BaseDataContext(ctx.typeFactory());
dflt = TypeUtils.fromLiteral(dataCtx, storageType, (SqlLiteral)col.expression);
}
cols.add(new ColumnDefinition(name, type, dflt));
}
createTblCmd.columns(cols);
List<SqlKeyConstraint> pkConstraints = createTblNode.columnList().getList().stream()
.filter(SqlKeyConstraint.class::isInstance)
.map(SqlKeyConstraint.class::cast)
.collect(Collectors.toList());
if (pkConstraints.size() > 1)
throw new IgniteSQLException("Unexpected amount of primary key constraints [" +
"expected at most one, but was " + pkConstraints.size() + "; " +
"querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
if (!F.isEmpty(pkConstraints)) {
Set<String> dedupSet = new HashSet<>();
List<String> pkCols = pkConstraints.stream()
.map(pk -> pk.getOperandList().get(1))
.map(SqlNodeList.class::cast)
.flatMap(l -> l.getList().stream())
.map(SqlIdentifier.class::cast)
.map(SqlIdentifier::getSimple)
.filter(dedupSet::add)
.collect(Collectors.toList());
createTblCmd.primaryKeyColumns(pkCols);
}
}
else { // CREATE AS SELECT.
ValidationResult res = planner.validateAndGetTypeMetadata(createTblNode.query());
// Create INSERT node on top of AS SELECT node.
SqlInsert sqlInsert = new SqlInsert(
createTblNode.query().getParserPosition(),
SqlNodeList.EMPTY,
createTblNode.name(),
res.sqlNode(),
null
);
createTblCmd.insertStatement(sqlInsert);
List<RelDataTypeField> fields = res.dataType().getFieldList();
List<ColumnDefinition> cols = new ArrayList<>(fields.size());
if (createTblNode.columnList() != null) {
// Derive column names from the CREATE TABLE clause and column types from the query.
List<SqlIdentifier> colNames = createTblNode.columnList().getList().stream()
.map(SqlIdentifier.class::cast)
.collect(Collectors.toList());
if (fields.size() != colNames.size()) {
throw new IgniteSQLException("Number of columns must match number of query columns",
IgniteQueryErrorCode.PARSING);
}
for (int i = 0; i < colNames.size(); i++) {
SqlIdentifier colName = colNames.get(i);
assert colName.isSimple();
RelDataType type = fields.get(i).getType();
cols.add(new ColumnDefinition(colName.getSimple(), type, null));
}
}
else {
// Derive column names and column types from the query.
for (RelDataTypeField field : fields)
cols.add(new ColumnDefinition(field.getName(), field.getType(), null));
}
createTblCmd.columns(cols);
}
if (createTblCmd.columns() == null) {
throw new IgniteSQLException("Column list or query should be specified for CREATE TABLE command",
IgniteQueryErrorCode.PARSING);
}
// Validate affinity key.
if (createTblCmd.affinityKey() != null) {
String affColName = null;
String val = createTblCmd.affinityKey();
if (val.startsWith("'")) {
if (val.length() == 1 || !val.endsWith("'")) {
throw new IgniteSQLException("Affinity key column name does not have trailing quote: " + val,
IgniteQueryErrorCode.PARSING);
}
val = val.substring(1, val.length() - 1);
if (F.isEmpty(val))
throw new IgniteSQLException("Affinity key cannot be empty", IgniteQueryErrorCode.PARSING);
affColName = val;
}
else {
for (ColumnDefinition col : createTblCmd.columns()) {
if (col.name().equalsIgnoreCase(val)) {
if (affColName != null) {
throw new IgniteSQLException("Ambiguous affinity column name, use single quotes " +
"for case sensitivity: " + val, IgniteQueryErrorCode.PARSING);
}
affColName = col.name();
}
}
}
String affColFinal = affColName;
if (affColName == null || createTblCmd.columns().stream().noneMatch(col -> affColFinal.equals(col.name()))) {
throw new IgniteSQLException("Affinity key column with given name not found: " + val,
IgniteQueryErrorCode.PARSING);
}
if (!createTblCmd.primaryKeyColumns().contains(affColName)) {
throw new IgniteSQLException("Affinity key column must be one of key columns: " + affColName,
IgniteQueryErrorCode.PARSING);
}
createTblCmd.affinityKey(affColName);
}
return createTblCmd;
}