Duplicate select-list columns collapse in $project, shifting every later result position

XMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Unknown
    • None
    • Affects Version/s: None
    • Component/s: Query
    • None
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Each select-list position is independent, so the same column may appear at more than one position.
      Hibernate ORM produces such select lists on its own for ordinary mappings. A $project stage is a
      BSON document, so repeating a field name collapses the entries. The stage then yields fewer fields
      than the select list has positions, and MongoResultSet maps positions to fields by index, so
      every position after the first duplicate reads the wrong field.

      Reproducer 1: a discriminator also mapped as an attribute

      @Entity(name = "Animal")
      @Table(name = "animals")
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
      static class Animal {
          @Id int id;
          String name;
          @Column(name = "type", insertable = false, updatable = false)
          String type;
      }
      
      @Entity(name = "Cat")
      @DiscriminatorValue("CAT")
      static class Cat extends Animal {
          int lives;
          String colour;
      }
      

      session.find(Animal.class, 1) emits six projection specifications, of which type appears
      twice:

      {"aggregate": "animals", "pipeline": [
        {"$match": {"_id": {"$eq": 1}}},
        {"$project": {"_id": true, "type": true, "name": true, "type": true, "colour": true, "lives": true}}
      ]}
      

      The document carries five fields, so reading position 6 fails:

      org.hibernate.JDBCException: Could not extract column [5] from JDBC ResultSet
        [Failed to get value from column [index: 5]]
      

      Reproducer 2: an entity reachable by two paths of one query

      No annotation is used unusually here. Owner is both the query root and the target of the
      association held by the embeddable of Shelter.

      @Entity(name = "Owner") @Table(name = "owners")
      static class Owner {
          @Id int id;
          String description;
          @ManyToOne Shelter shelter;
      }
      
      @Entity(name = "Shelter") @Table(name = "shelters")
      static class Shelter {
          @Id int id;
          @Embedded Location location;
      }
      
      @Embeddable
      static class Location {
          String town;
          @ManyToOne Owner owner;
      }
      

      _id and description are each projected twice, so seven specifications produce five fields:

      {"aggregate": "owners", "pipeline": [
        {"$lookup": {"from": "shelters", "localField": "shelter_id", "foreignField": "_id", "as": "#s1_0"}},
        {"$unwind": {"path": "$#s1_0", "preserveNullAndEmptyArrays": true}},
        {"$match": {"_id": {"$eq": 1}}},
        {"$project": {"_id": true, "description": true, "s1_0#_id": "$#s1_0._id",
                      "_id": true, "description": true, "shelter_id": true, "s1_0#town": "$#s1_0.town"}}
      ]}
      
      org.hibernate.JDBCException: Could not extract column [6] from JDBC ResultSet
        [Invalid column index [6]; cannot be under 1 or over the current number of fields [5]]
      

      Severity

      The overrun at the end of the row is what raises the exception. Positions between the duplicate and
      the end of the row resolve to the wrong field and are read without complaint, so a select list whose
      duplicate is not followed by an overrun returns wrong values silently.

      Proposed fix

      Give each select-list position its own projection field name, and have MongoResultSet resolve
      positions against the names the translator assigned rather than against whatever the document
      happens to contain.

      Acceptance

      Both reproducers above load the entity and read the intended values, asserted by value and not
      merely by the absence of an exception.

            Assignee:
            Unassigned
            Reporter:
            Jeffrey Yemin
            None
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: