Repository: duckdb/duckdb
File: src/parser/transform/statement/transform_alter_table.cpp (C++)

Code before:
	 *		`ALTER TABLE t ALTER col SET TYPE <type> USING NULL;`
	 *		depending on the parameter `info_with_null_placeholder`
	 */
	AddToMultiStatement(multi_statement, std::move(info_with_null_placeholder));

	// 2. `UPDATE t SET u = <expression>;`
	AddUpdateToMultiStatement(multi_statement, column_name, stmt.relation->relname, expression);

	// 3. `ALTER TABLE t ALTER u SET DEFAULT <expression>;`
	// Reinstate the original default expression.
	AddToMultiStatement(multi_statement, make_uniq<SetDefaultInfo>(data, column_name, std::move(expression)));
	return multi_statement;
}

bool IsMaterialized(const ParsedExpression &expr) {
	if (expr.GetExpressionClass() == ExpressionClass::CONSTANT || expr.GetExpressionClass() == ExpressionClass::CAST ||
	    expr.GetExpressionClass() == ExpressionClass::COLUMN_REF // FIXME: This could be a problem if the referred
	                                                             // column has an unmaterialized default expression
	) {
		return true;
	}
	const bool is_function = expr.GetExpressionClass() == ExpressionClass::FUNCTION;
	if (is_function) {
		auto &function_expression = expr.Cast<FunctionExpression>();
		if (function_expression.function_name == "struct_insert") {
			return true;
		}
	}

	return false;
}

unique_ptr<SQLStatement> Transformer::TransformAlter(duckdb_libpgquery::PGAlterTableStmt &stmt) {
	D_ASSERT(stmt.relation);
	if (stmt.cmds->length != 1) {
		throw ParserException("Only one ALTER command per statement is supported");
	}

	auto result = make_uniq<AlterStatement>();
	auto qualified_name = TransformQualifiedName(*stmt.relation);

	// Check the ALTER type.
	for (auto c = stmt.cmds->head; c != nullptr; c = c->next) {
		auto command = PGPointerCast<duckdb_libpgquery::PGAlterTableCmd>(c->data.ptr_value);
		AlterEntryData data(qualified_name.catalog, qualified_name.schema, qualified_name.name,
		                    TransformOnEntryNotFound(stmt.missing_ok));

		switch (command->subtype) {
		case duckdb_libpgquery::PG_AT_AddColumn: {
			auto column_name_list = PGPointerCast<duckdb_libpgquery::PGList>(command->def_list->head->data.ptr_value);
			auto column_def = PGPointerCast<duckdb_libpgquery::PGColumnDef>(command->def_list->tail->data.ptr_value);

Review comment (suggestion):
I'm not sure what this is trying to do?
It looks like an optimization to take the fast path in as many cases as possible?

Can you clarify what the thought process here is?
I think `expr.IsScalar()` would cover all of these, or is that too naive?

If it doesn't, I think we should rewrite this logic to use the `ParsedExpressionIterator`

Code after:
	return multi_statement;
}

unique_ptr<SQLStatement> Transformer::TransformAlter(duckdb_libpgquery::PGAlterTableStmt &stmt) {
	D_ASSERT(stmt.relation);
	if (stmt.cmds->length != 1) {
		throw ParserException("Only one ALTER command per statement is supported");
	}

	auto result = make_uniq<AlterStatement>();
	auto qualified_name = TransformQualifiedName(*stmt.relation);

	// Check the ALTER type.
	for (auto c = stmt.cmds->head; c != nullptr; c = c->next) {
		auto command = PGPointerCast<duckdb_libpgquery::PGAlterTableCmd>(c->data.ptr_value);
		AlterEntryData data(qualified_name.catalog, qualified_name.schema, qualified_name.name,
		                    TransformOnEntryNotFound(stmt.missing_ok));

		switch (command->subtype) {
		case duckdb_libpgquery::PG_AT_AddColumn: {
			auto column_name_list = PGPointerCast<duckdb_libpgquery::PGList>(command->def_list->head->data.ptr_value);
			auto column_def = PGPointerCast<duckdb_libpgquery::PGColumnDef>(command->def_list->tail->data.ptr_value);
			if (stmt.relkind != duckdb_libpgquery::PG_OBJECT_TABLE) {
				throw ParserException("Adding columns is only supported for tables");
			}
			if (column_def->category == duckdb_libpgquery::COL_GENERATED) {
				throw ParserException("Adding generated columns after table creation is not supported yet");
			}
			auto column_entry = TransformColumnDefinition(*column_def);
			if (column_def->constraints) {
				for (auto cell = column_def->constraints->head; cell != nullptr; cell = cell->next) {
					auto pg_constraint = PGPointerCast<duckdb_libpgquery::PGConstraint>(cell->data.ptr_value);
					auto constraint = TransformConstraint(*pg_constraint, column_entry, 0);
					if (!constraint) {
						continue;
					}
					throw ParserException("Adding columns with constraints not yet supported");
				}
			}
			auto column_names = TransformNameList(*column_name_list);
			if (column_names.empty()) {
				throw InternalException("Expected a name");
			}
			column_entry.SetName(column_names.back());
			if (column_names.size() == 1) {
				// ADD COLUMN
				if (!column_entry.HasDefaultValue() ||
				    column_entry.DefaultValue().GetExpressionClass() == ExpressionClass::CONSTANT) {
					result->info =
					    make_uniq<AddColumnInfo>(std::move(data), std::move(column_entry), command->missing_ok);
					break;