### Description

`IndexResolver` tries to create a tree structure for field names that contain dots (e.g. ECS) which can conflict with fields in other indices in which case all underlying fields end up being discarded as unsupported.

Consider field "file.name" as keyword in "index-1" and "file" as keyword in "index-2". This results in the following mapping from field caps:
```json
"indices": ["index-1", "index-2"],
"fields":
{
 "file.name":
 {
  "keyword":
  {
   "type": "keyword",
   "metadata_field": false,
   "searchable": true,
   "aggregatable": true,
   "indices": ["index-1"]
  },
  "unmapped":
  {
   "type": "unmapped",
   "metadata_field": false,
   "searchable": false,
   "aggregatable": false,
   "indices": ["index-2"]
  }
 },
 "file":
 {
  "keyword":
  {
   "type": "keyword",
   "metadata_field": false,
   "searchable": true,
   "aggregatable": true,
   "indices": ["index-2"]
  },
  "object":
  {
   "type": "object",
   "metadata_field": false,
   "searchable": false,
   "aggregatable": false,
   "indices": ["index-1"]
  }
 }
}
```

Notice that `file` is mapped both as an `object` in `index-1` and `keyword` in `index-2`. IndexResolver sees the conflict and thus discards as unsupported all the fields under it, including `file.name` hence why the ESQL query `where file.name is not null` fails.
When a projection is added `where file.name is not null |  keep file.name`, IndexResolver doesn't ask the mapping information for `file` which removes the conflict and thus `file.name` can be resolved.

The main fix here is to NOT discard fields under an object leaf as unsupported if they have no conflict. In this example `file.name` is mapped as keyword in one index and unmapped in the other so across all indices it will have the same type.
Furthermore listing the field as missing instead of say unsupported is incorrect.
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java
import java.util.function.Function;
import static java.util.Map.entry;
import static org.elasticsearch.xpack.esql.io.stream.PlanNameRegistry.Entry.of;
import static org.elasticsearch.xpack.esql.io.stream.PlanNameRegistry.PlanReader.readerFromPlanReader;
import static org.elasticsearch.xpack.esql.io.stream.PlanNameRegistry.PlanWriter.writerFromPlanWriter;
/**
 * A utility class that consists solely of static methods that describe how to serialize and
 * deserialize QL and ESQL plan types.
 * <P>
 * All types that require to be serialized should have a pair of co-located `readFoo` and `writeFoo`
 * methods that deserialize and serialize respectively.
 * <P>
 * A type can be named or non-named. A named type has a name written to the stream before its
 * contents (similar to NamedWriteable), whereas a non-named type does not (similar to Writable).
 * Named types allow to determine specific deserialization implementations for more general types,
 * e.g. Literal, which is an Expression. Named types must have an entries in the namedTypeEntries
 * list.
 */
public final class PlanNamedTypes {
    private PlanNamedTypes() {}
    /**
     * Determines the writeable name of the give class. The simple class name is commonly used for
     * {@link NamedWriteable}s and is sufficient here too, but it could be almost anything else.
     */
    public static String name(Class<?> cls) {
        return cls.getSimpleName();
    }
    static final Class<org.elasticsearch.xpack.ql.expression.function.scalar.UnaryScalarFunction> QL_UNARY_SCLR_CLS =
        org.elasticsearch.xpack.ql.expression.function.scalar.UnaryScalarFunction.class;
    static final Class<UnaryScalarFunction> ESQL_UNARY_SCLR_CLS = UnaryScalarFunction.class;