Support $jsonSchema validation on collection creation

XMLWordPrintableJSON

    • Type: New Feature
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: None
    • Component/s: Model
    • None
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Collections are created bare today: the create command carries no options, so MongoDB enforces nothing about document shape. This adds opt-in server-side write validation: derive a $jsonSchema from the entity mapping and attach it as the collection's validator when the collection is created, so the server rejects a write whose shape does not match the mapping.

      Configuration

      Opt-in, via a new Hibernate property: com.mongodb.hibernate.schema.validation (boolean, default false). It matters only when a create command is generated, so with schema-generation action update, validate or none it has no effect.

      Derived schema

      The generator emits one document schema per entity mapped to the collection, built from that entity's identifier, the discriminator of a single-table hierarchy, and its property closure. A collection one entity maps gets that schema directly. A collection several entities map gets an anyOf over their schemas, so each writer's document is validated against exactly its own shape and a document mixing fields from two writers is rejected. Columns are ordered by the table's column order where present, with the entity's own columns replacing same-named table columns, because the table's name-keyed column map keeps only one of two same-named columns (the same upstream defect that drops colliding top-level columns). A table no entity maps gets a schema with a synthetic _id of bsonType objectId.

      Every mapped column becomes a property with a bsonType from the column's JDBC type, the same mapping the write path uses:

      Domain type bsonType
      Boolean bool
      Character, String string
      Byte, Integer int
      Long long
      Double double
      BigDecimal decimal
      byte[] binData
      ObjectId objectId
      Instant date
      @Struct embeddable object, recursively
      array or collection array with items

      Rules, applied at every object level, the collection and each nested struct:

      • properties for every mapped field
      • required covers exactly the non-nullable fields: a field whose bsonType is a single type must be present; a field whose list contains null may be omitted or stored null. This is SQL's shape, and it holds uniformly at every level
      • a nullable field gets a two-element bsonType list, the type and null. Nulls bind as BSON null and are stored, never omitted, so a single-element bsonType would reject every stored null
      • nullability comes from the mapping: @Column(nullable = false) is tracked everywhere, and a primitive field is non-nullable at every level, derived by reflection for embeddable fields, except that a primitive leaf inside a nullable plain embeddable allows null, because a null plain embeddable binds each of its flattened columns as null. Jakarta validation constraints are mirrored into nullability only when the application supplies hibernate-validator on the classpath, only at the entity level, and inside an embeddable only when the embeddable property is @Valid and itself not-null-constrained
      • an enum field is stored as its name for @Enumerated(EnumType.STRING) and its ordinal otherwise, so the schema emits enum with exactly those literals, and a nullable enum field appends a null literal. Enum-typed array elements get the same closed set as the items schema
      • a collection classified as Set semantics emits uniqueItems: true: a Java Set cannot contain duplicates, so the write path can never violate the constraint and it only binds external writers
      • @Column(length) is deliberately not expressed as maxLength: the write path does not enforce string length, so emitting it would newly reject writes that currently succeed
      • additionalProperties: false: a document carrying a field the mapping does not know about is rejected
      • a plain embeddable without @Struct is stored flattened, so its fields are ordinary top-level properties
      • a composite @EmbeddedId arrives as _id.-encoded columns and becomes one closed _id object property

      Example command for a single entity; title and the struct are nullable, so required lists only _id:

      {"create": "books", "validator": {"$jsonSchema": {
        "bsonType": "object",
        "properties": {
          "_id": {"bsonType": "long"},
          "title": {"bsonType": ["string", "null"]},
          "author": {"bsonType": ["object", "null"],
                     "properties": {"name": {"bsonType": ["string", "null"]}},
                     "additionalProperties": false}
        },
        "required": ["_id"],
        "additionalProperties": false}}}

      Scope

      The validator is attached to the create command and nothing else. Existing collections are never re-validated: alter table remains unsupported. With validationLevel strict the server refuses to apply a stricter validator while any existing document violates it, which would make a routine mapping change fail startup, and moderate would grandfather old documents silently. validationAction (error) and validationLevel (strict) stay at server defaults.

      The integration test suite runs with the validator on (schema-generation action create-drop plus the property in the test harness configuration), so every type and document shape the suite exercises is written through a validated collection: a field type added to the write path without a matching generator entry fails the suite as soon as a test writes it.

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

              Created:
              Updated: