tuplesedgedb

How do I define a specific, custom tuple type in EdgeDB?


I would like to define a tuple of two numbers as below:

scalar type container extending tuple<int16, int16> {
  constraint expression on (
      __subject__.0 >= 0
      and __subject__.1 >= 0
      and __subject__.0 < 256
      and __subject__.1 < 256
      and __subject__.0 <= __subject__.1
    )
}

Is this possible?

I would prefer this over defining a full object type, because I am hoping a tuple type will also serialize to a tuple type in my programming language (TypeScript and Rust), while an object would serialize to an interface/struct.


Solution

  • As of version 4.2 it's not possible to extend the tuple. Most you can do is to use a constrained int inside of the tuple:

    module default {
    
        scalar type uint8 extending int16 {
            constraint max_value(255);
            constraint min_value(0);
        }
    
        type WithTuple {
            container: tuple<uint8, uint8>;
    
            constraint expression on (
                .container.0 <= .container.1
            ) { errmessage := 'container start must be <= the container end' };
    
       }
    }
    

    Inserting a value gives a custom error:

    insert WithTuple { container := (1, 0) };
    
    # ConstraintViolationError: container start must be <= the container end
    

    Alternatively, you can use range which funny enough by default watches the lower bound to be less than the upper, but is not able to constraint the min/max, so we must do it explicitly:

    module default {
    
        type WithRange {
            container: range<int32>; # Note: int16 is not supported for range
            
            constraint expression on (
                0 <= range_get_lower(.container) and range_get_upper(.container) <= 255
            ) { errmessage := 'range values must be 0 at minimum and 255 at maximum' };
        }
    }
    

    Here the start <= end invariant has default error message.

    insert WithRange { container := range(1, 0) };
    
    # InvalidValueError: range lower bound must be less than or equal to range upper bound
    

    And we can see that the custom one works too:

    insert WithRange { container := range(-1, 0) };
    
    # ConstraintViolationError: range values must be 0 at minimum and 255 at maximum
    

    Note that since range is not overloaded with int16, but it is with int32, we might try to define our uint8 as:

        scalar type uint8_32 extending int32 {
            constraint min_value(0);
            constraint max_value(255);
        }
    

    But we would discover, that range won't accept that:

        type WithRange {
            container: range<uint8_32>;
        }
    

    Here running edgedb migration create yields:

    edgedb error: UnsupportedFeatureError: unsupported range subtype: default::uint8_32
    

    Sadly, the range does not satisfy your need to serialize to tuple. With range in TypeScript you get the

    import { Range } from "edgedb"
    
    // simplified
    type Range<T extends number | Date | LocalDate | LocalDateTime | Duration> {  
      lower: T | null;
      upper: T | null;
      inc_lower: boolean;
      inc_upper: boolean;
      empty?: undefined;
    }
    

    Still this might be an interesting option, given the possibility of an empty range.