Sign in

    partiql-value

    The PartiQL data model for MoonBit, with a PartiQL-encoded Ion codec.

    partiql
    sql
    query
    ion
    data-model
    Download zip
    Author
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    12 hours ago
    Downloads
    11

    Dependencies

    #moonrockz/partiql-value

    The PartiQL data model for MoonBit, with structural identity, SQL equality, grouping equivalence, ordering and a PartiQL-encoded Ion codec.

    #Installation and imports

    moon add moonrockz/partiql-value

    Add the packages you use to moon.pkg:

    import {
    "moonrockz/partiql-value" @value,
    "moonrockz/partiql-value/ion" @partiql_ion,
    "moonrockz/ion/ion" @ion_core,
    }

    The checked examples use moonrockz/expect for assertions. Add it with moon add moonrockz/expect and put this import in moon.pkg when copying the test blocks:

    import { "moonrockz/expect" } for "test"

    PackageContent
    moonrockz/partiql-valueValue, collections, maps, graphs, datetime and interval types, and value relations
    moonrockz/partiql-value/ionSingle-value codecs, text/binary datagrams and Ion Schema validation
    moonrockz/partiql-value/arbitraryQuickcheck generators and shrinkable wrappers

    #Constructing values

    Value(x) converts Bool, Int, Int64, BigInt, Double, String, Ion decimals, validated datetime/interval types, Bag, Tuple, MapValue, GraphValue and arrays of convertible elements. An existing Value passes through. A BigInt becomes Int when it fits Int64. An Ion value is boxed without lowering it.

    test "construct values" {
    let list = @value.Value([1, 2, 3])
    @expect.expect(list).to_equal(@value.Value::from_list([@value.Value::from_int(1), @value.Value::from_int(2), @value.Value::from_int(3)]))
    }

    Use Value::from_* when the payload type is known. Every payload-bearing variant has a typed conversion:

    PayloadConversion
    Bool, Int64, BigIntfrom_bool, from_int, from_bigint
    Double, Ion Decimal, Stringfrom_float, from_decimal, from_string
    Bytesfrom_blob or from_clob, chosen explicitly
    Date, Time, Timestampfrom_date, from_time, from_timestamp
    IntervalYM, IntervalDTfrom_interval_ym, from_interval_dt
    Array[Value], Bag, Tuplefrom_list, from_bag, from_tuple
    MapValue, GraphValue, Ion IonValuefrom_map, from_graph, from_ion

    from_bigint normalizes integers to Int when they fit Int64. Other typed conversions retain the payload's kind and storage. Domain constructors validate inputs; value conversions wrap the validated result. from_list shares its backing array, while generic Value(array) maps into a new list.

    The concrete parameter types resolve nested domain constructor names:

    test "typed bag and date conversions" {
    let bag = @value.Value::from_bag(@value.Bag([@value.Value(1), @value.Value(2)]))
    let date = @value.Value::from_date(@value.Date(year=2024, month=2, day=29))
    @expect.expect_all(soft => {
    soft.expect(bag is @value.Value::Bag(b) && b.length() == 2).to_be_true()
    soft.expect(date is @value.Value::Date(d) && d.day == 29).to_be_true()
    })
    }

    Tuple(fields) builds the domain tuple. Value::from_tuple(tuple) wraps it as a PartiQL value and is equivalent to Value(tuple):

    test "build and wrap a tuple" {
    let value = @value.Value::from_tuple(@value.Tuple([
    ("a", @value.Value(1)), ("a", @value.Value(2)),
    ]))
    guard value is @value.Value::Tuple(tuple) else {
    fail("expected a tuple value")
    }
    @expect.expect(tuple.get_all("a")).to_equal([@value.Value(1), @value.Value(2)])
    }

    Use enum variants in patterns, as above. They also remain available for construction for compatibility. Bag, Tuple, Date, Time, Timestamp, IntervalYM and IntervalDT share names with variants, so a standalone let tuple = @value.Tuple(fields) still needs an expected result type or qualified Tuple::Tuple(...). Prefer typed wrapping functions when building a Value, for example Value::from_tuple(@value.Tuple(fields)).

    A tuple preserves duplicate names and drops MISSING fields. A bag keeps MISSING and duplicates. Bags retain the supplied backing array; copy it before construction if you will mutate it later. Tuple, map and graph constructors create their own arrays. Collection accessors return read-only views in stored order.

    Use explicit Value::Null and Value::Missing. Bytes have two meanings, so choose Value::from_blob(bytes) or Value::from_clob(bytes) explicitly. Arrays become lists; use a Bag for an unordered collection. Conversions do not perform SQL CAST coercions.

    Implement ToValue to support a domain type with Value(x):

    struct CustomerId(Int)

    impl @value.ToValue for CustomerId with fn to_value(self) {
    @value.Value(self.0)
    }

    test "domain conversion" {
    @expect.expect(@value.Value(CustomerId(7))).to_equal(@value.Value::from_int(7))
    }

    The former Type::new(...) entry points remain deprecated aliases. Replace them with Type(...), or Type::Type(...) where qualification is needed. Value::integer remains available as the legacy spelling of Value::from_bigint.

    #Datetimes and intervals

    Dates use the proleptic Gregorian calendar with years -999999999..999999999. Times retain fractional precision and an optional offset in minutes east of UTC; None means WITHOUT TIME ZONE. Invalid fields raise DateTimeError. Intervals normalize their parts and raise IntervalError::Overflow when the representation cannot hold the result.

    test "datetime and interval constructors" {
    let date : @value.Date = @value.Date(year=2024, month=2, day=29)
    let time : @value.Time = @value.Time(
    hour=12, minute=30, second=0, nanos=125000000, precision=3, offset=Some(60),
    )
    let timestamp : @value.Timestamp = @value.Timestamp(date, time)
    let interval : @value.IntervalDT = @value.IntervalDT(0, -250000000)
    @expect.expect((timestamp.time.precision, interval.is_negative(), interval.nanos_part())).to_equal((3, true, 250000000L))
    }

    #Maps and graphs

    MapValue checks keys against the declared key family and rejects NULL, MISSING, nonscalar and duplicate keys. Duplicate keys use grouping equivalence. MISSING values are dropped after all keys are checked. The declared value type is metadata; construction does not coerce values to it.

    Graphs have separate node and edge id namespaces. Construction rejects unknown endpoints, duplicate ids within a namespace and MISSING payloads. Labels are deduplicated; absent payloads use None.

    test "map and graph constructors" {
    let map = @value.MapValue(
    key_type=@value.MapType::String, value_type=@value.MapType::Dynamic,
    [(@value.Value("a"), @value.Value(1))],
    )
    let node = @value.GraphNode(id="n", labels=["person", "person"])
    let edge = @value.GraphEdge(id="e", ends=@value.Ends::Directed(from="n", to="n"))
    let graph = @value.GraphValue([node], [edge])
    @expect.expect((map.length(), graph.nodes()[0].labels().length(), graph.edges().length())).to_equal((1, 1, 1))
    }

    #Choosing a relation

    RelationMeaning
    == via EqStructural identity: same variant and contents; bags and tuples as multisets; decimal scale counts
    sql_equalsSQL equality over values: MISSING takes precedence over NULL; numbers compare across types
    eqgGrouping equivalence: NULL equals NULL, MISSING equals MISSING, NaN equals NaN
    compareORDER BY total order, choosing NullsFirst or NullsLast

    Boxed Ion values compare through their lowered representation in semantic relations. Structural identity retains the Ion representation. The evaluator's = operator adds its own absence and datetime rules; use evaluate_text for query semantics.

    test "boxed Ion equality" {
    let symbol = @value.Value(@ion_core.IonValue::symbol("hello"))
    @expect.expect(@value.sql_equals(symbol, @value.Value("hello"))).to_equal(@value.Value::from_bool(true))
    }

    Value::lower lowers the outer Ion representation; lower_deep also lowers nested Ion. An unresolved symbol raises LowerError. fold walks deeply nested values without relying on the call stack.

    #Ion codecs and datagrams

    @partiql_ion.from_ion and to_ion read and write PartiQL-encoded Ion, including $bag, $missing and $ion. from_ion_data boxes raw Ion; to_ion_data preserves boxed Ion at every depth. Choose a matching pair: a raw $bag annotation must stay boxed when reading Ion-preserving output.

    Value::from_ion boxes raw Ion without decoding PartiQL annotations. It is equivalent to Value(ion_value) or the codec's from_ion_data, and differs from the codec's from_ion decoding operation.

    Datagram readers return ordered arrays without implicit bag conversion. read_bytes detects binary Ion by its version marker; otherwise it requires valid UTF-8 text. write_bytes writes a binary datagram with a local symbol table. write_text writes one value per line, with a final newline for nonempty input and optional pretty=true layout.

    test "binary datagram round trip" {
    let values = @partiql_ion.read_text("1 $missing::null $bag::[2, 3] $ion::foo")
    let bytes = @partiql_ion.write_bytes(values)
    @expect.expect(@partiql_ion.read_bytes(bytes)).to_equal(values)
    }

    Readers accept optional catalog~ for shared symbol tables and validation~ for Ion Schema validation. Load a schema from text or binary bytes with load_schema_bytes. Validation::elements validates each raw element; Validation::document validates a complete datagram. Validation runs before PartiQL decoding and collects violations with paths. Schemas describe physical Ion types and annotations, including $bag and $missing.

    test "validate raw Ion" {
    let schema = @partiql_ion.load_schema_bytes(
    b"$ion_schema_2_0 schema_header::{} type::{name: row, type: struct} schema_footer::{}",
    )
    let validation = @partiql_ion.Validation::elements(schema, "row")
    let values = @partiql_ion.read_text("{a: 1} {a: 2}", validation~)
    @expect.expect(values.length()).to_equal(2)
    }

    CodecError distinguishes malformed and unsupported encodings. IonDataError distinguishes UTF-8, text/binary Ion, codec, schema and output failures. Violations carries every validation path and message.

    #Property tests

    Import moonrockz/partiql-value/arbitrary and moonbitlang/core/quickcheck for tests. ArbValue, ArbIon and ArbTimestamp implement generation and shrinking. gen_value(size, state) accepts an explicit recursion budget; nonpositive sizes produce leaves. Generators include absent values, numeric boundaries, boxed Ion, maps and graphs.

    See the property testing guide for a runnable codec property and generator controls.

    See the repository's architecture, Ion semantics and datagram/session design. The checked examples above run through mise run test:docs.

    ToValue

    pub(open) trait ToValue {
    fn to_value(Self) -> Value
    }

    Conversion to a PartiQL value. Implement this trait for domain types that have one unambiguous representation, then call Value(x). Conversions preserve types; they do not perform SQL CAST coercions.
    impl ToValue for Bool
    impl ToValue for Int
    impl ToValue for Int64
    impl ToValue for Double
    impl ToValue for String
    impl ToValue for Array[T]
    impl ToValue for Ion

    DateTimeError

    pub(all) suberror DateTimeError {
    InvalidDate(String)
    InvalidTime(String)
    } derive(Eq,
    Debug
    )

    A date, time or timestamp field that is out of range.

    GraphError

    pub(all) suberror GraphError {
    DuplicateNodeId(String)
    DuplicateEdgeId(String)
    UnknownNode(String)
    MissingPayload(String)
    } derive(Eq,
    Debug
    )

    Why a graph could not be built.

    IntervalError

    pub(all) suberror IntervalError {
    Overflow(String)
    } derive(Eq,
    Debug
    )

    An interval that does not fit the representation.

    LowerError

    pub(all) suberror LowerError {
    SymbolWithoutText
    } derive(Eq,
    Debug
    )

    An Ion value that has no PartiQL equivalent.

    MapError

    pub(all) suberror MapError {
    InvalidKey(String)
    DuplicateKey(String)
    DynamicKeyType
    } derive(Eq,
    Debug
    )

    Why a MAP could not be built.

    Bag

    pub struct Bag {
    // private fields
    }

    A PartiQL bag: an unordered collection that allows duplicates.
    impl Eq for Bag
    impl Debug for Bag
    impl ToValue for Bag

    Bag::Bag

    #alias(new, deprecated="Use Bag(...) instead")
    fn Bag::Bag(items : Array[Value]) -> Bag

    Build a bag, retaining duplicates and the input order for output. The backing array is shared with items; copy it first if it will be mutated.

    Bag::items

    fn Bag::items(self : Bag) -> ArrayView[Value]

    A read-only view of the elements in their stored order, including duplicates.

    Bag::length

    fn Bag::length(self : Bag) -> Int

    The number of elements, counting duplicates.

    Date

    pub struct Date {
    year : Int
    month : Int
    day : Int
    } derive(
    Debug
    )

    A calendar date (proleptic Gregorian).
    impl Eq for Date
    impl ToValue for Date

    Date::Date

    #alias(new, deprecated="Use Date(...) instead")
    fn Date::Date(year~ : Int, month~ : Int, day~ : Int) -> Date raise DateTimeError

    Build a Gregorian date with a year in -999999999..999999999. Raise DateTimeError::InvalidDate for an invalid year, month or day, including February 29 in a non-leap year.

    test {
    let leap_day : @value.Date = @value.Date(year=2024, month=2, day=29)
    @expect.expect(leap_day.day).to_equal(29)
    }

    Ends

    pub(all) enum Ends {
    Directed(from~ : String, to~ : String)
    Undirected(String, String)
    } derive(
    Debug
    )

    The ends of a graph edge (RFC 0025): an ordered pair for a directed edge, an unordered pair for an undirected edge. Ends name node ids.
    impl Eq for Ends

    GraphEdge

    pub struct GraphEdge {
    id : String
    ends : Ends
    payload : Value?
    // private fields
    }

    A graph edge: id, labels, ends and an optional payload.

    GraphEdge::GraphEdge

    #alias(new, deprecated="Use GraphEdge(...) instead")
    fn GraphEdge::GraphEdge(id~ : String, ends~ : Ends, labels? : Array[String], payload? : Value?) -> GraphEdge

    Build an edge and remove duplicate labels in first-seen order. Endpoint existence, id uniqueness and MISSING payloads are checked by GraphValue.

    GraphEdge::labels

    fn GraphEdge::labels(self : GraphEdge) -> ArrayView[String]

    The labels, without duplicates, in first-seen order.

    GraphNode

    pub struct GraphNode {
    id : String
    payload : Value?
    // private fields
    }

    A graph node: its id from the source data, its labels (a set, kept in first-seen order) and an optional payload. An absent payload is not NULL: it acts as MISSING in queries.

    GraphNode::GraphNode

    #alias(new, deprecated="Use GraphNode(...) instead")
    fn GraphNode::GraphNode(id~ : String, labels? : Array[String], payload? : Value?) -> GraphNode

    Build a node and remove duplicate labels in first-seen order. Id uniqueness and MISSING payloads are checked when building GraphValue.

    GraphNode::labels

    fn GraphNode::labels(self : GraphNode) -> ArrayView[String]

    The labels, without duplicates, in first-seen order.

    GraphValue

    pub struct GraphValue {
    // private fields
    }

    A PartiQL graph (RFC 0025). Node ids are unique among nodes, edge ids among edges (two namespaces), and every edge end names a node.
    impl Eq for GraphValue

    GraphValue::GraphValue

    #alias(new, deprecated="Use GraphValue(...) instead")
    fn GraphValue::GraphValue(nodes : Array[GraphNode], edges : Array[GraphEdge]) -> GraphValue raise GraphError

    Build a graph from copies of the node and edge arrays. Raise GraphError for duplicate ids within either namespace, unknown endpoints, or MISSING payloads. Node and edge ids may share a name.

    GraphValue::edges

    A read-only view of edges in their input order.

    GraphValue::nodes

    A read-only view of nodes in their input order.

    IntervalDT

    pub struct IntervalDT {
    seconds : Int64
    nanos : Int
    } derive(
    Debug
    )

    A day-time interval: seconds + nanos / 10^9, with nanos in 0..999,999,999 (floor form).
    impl Eq for IntervalDT

    IntervalDT::IntervalDT

    #alias(new, deprecated="Use IntervalDT(...) instead")
    fn IntervalDT::IntervalDT(seconds : Int64, nanos : Int64) -> IntervalDT raise IntervalError

    Normalize seconds + nanos / 10^9 to floor form, carrying excess or negative nanoseconds into seconds. Raise IntervalError::Overflow if the normalized seconds or their magnitude cannot fit Int64.

    IntervalDT::days

    fn IntervalDT::days(self : IntervalDT) -> Int64

    Whole days in the absolute magnitude of the interval.

    IntervalDT::from_parts

    fn IntervalDT::from_parts(negative~ : Bool, days~ : Int64, hours~ : Int64, minutes~ : Int64, seconds~ : Int64, nanos~ : Int64) -> IntervalDT raise IntervalError

    Combine day, hour, minute, second and nanosecond parts, then negate when negative is true. Normalize overflowing parts using exact arithmetic. Raise IntervalError::Overflow if the normalized seconds or their magnitude cannot fit Int64.

    IntervalDT::hours

    fn IntervalDT::hours(self : IntervalDT) -> Int64

    Remaining hours in the absolute magnitude, in 0..23.

    IntervalDT::is_negative

    fn IntervalDT::is_negative(self : IntervalDT) -> Bool

    Whether the interval is below zero, including negative fractional seconds.

    IntervalDT::minutes

    fn IntervalDT::minutes(self : IntervalDT) -> Int64

    Remaining minutes in the absolute magnitude, in 0..59.

    IntervalDT::nanos_part

    fn IntervalDT::nanos_part(self : IntervalDT) -> Int64

    Fractional nanoseconds in the absolute magnitude, in 0..999999999.

    IntervalDT::seconds_part

    fn IntervalDT::seconds_part(self : IntervalDT) -> Int64

    Remaining whole seconds in the absolute magnitude, in 0..59.

    IntervalYM

    pub struct IntervalYM {
    months : Int64
    } derive(
    Debug
    )

    A year-month interval, as a signed number of months.
    impl Eq for IntervalYM

    IntervalYM::IntervalYM

    #alias(new, deprecated="Use IntervalYM(...) instead")
    fn IntervalYM::IntervalYM(months : Int64) -> IntervalYM raise IntervalError

    Build a signed month count. Raise IntervalError::Overflow for Int64's minimum value, whose positive magnitude cannot be represented.

    IntervalYM::from_parts

    fn IntervalYM::from_parts(negative~ : Bool, years~ : Int64, months~ : Int64) -> IntervalYM raise IntervalError

    Combine years * 12 + months, then negate when negative is true. Parts may exceed their usual calendar ranges. Raise IntervalError::Overflow if the signed total or its magnitude does not fit Int64.

    IntervalYM::is_negative

    fn IntervalYM::is_negative(self : IntervalYM) -> Bool

    Whether the signed month count is below zero. Zero is never negative.

    IntervalYM::months_part

    fn IntervalYM::months_part(self : IntervalYM) -> Int64

    Remaining months in the absolute magnitude, in 0..11.

    IntervalYM::years

    fn IntervalYM::years(self : IntervalYM) -> Int64

    Whole years in the absolute magnitude of the interval.

    MapType

    pub(all) enum MapType {
    Dynamic
    Bool
    TinyInt
    SmallInt
    Integer
    BigInt
    Numeric
    Decimal
    Real
    Double
    String
    Char
    Varchar
    Date
    Time
    Timestamp
    } derive(Eq,
    Debug
    )

    The declared key or value type of a MAP (RFC 0104). The names are the static type names of partiql-tests' $map::<key>::<value>:: encoding.

    MapType::admits

    fn MapType::admits(self : MapType, key : Value) -> Bool

    Whether a map with this key type can hold key (RFC 0104): the key is of the same type family. Every number fits a numeric key type, and every string a character key type. DYNAMIC holds no key (RFC 0104 leaves it for future work), and NULL, MISSING and non-scalar values are never keys.

    MapType::from_name

    fn MapType::from_name(name : String) -> MapType?

    The type for a name; int is accepted for integer.

    MapType::name

    fn MapType::name(self : MapType) -> String

    The type name as partiql-tests writes it.

    MapValue

    pub struct MapValue {
    key_type : MapType
    value_type : MapType
    // private fields
    }

    A PartiQL MAP: unique scalar keys, no MISSING values. The stored order is the insertion order (for output); equality and ordering ignore it.
    impl Eq for MapValue
    impl ToValue for MapValue

    MapValue::MapValue

    #alias(new, deprecated="Use MapValue(...) instead")
    fn MapValue::MapValue(key_type~ : MapType, value_type~ : MapType, entries : Array[(Value, Value)]) -> MapValue raise MapError

    Builds a MAP. Entries whose value is MISSING are dropped (RFC 0104). Keys are lowered first; NULL, MISSING and non-scalar keys, and keys equal under eqg to an earlier key, raise.

    MapValue::entries

    fn MapValue::entries(self : MapValue) -> ArrayView[(Value, Value)]

    A read-only view of the retained entries in insertion order. Keys are lowered and entries with MISSING values have already been removed.

    MapValue::length

    fn MapValue::length(self : MapValue) -> Int

    The number of retained entries after dropping MISSING values.

    NullOrder

    pub(all) enum NullOrder {
    NullsFirst
    NullsLast
    } derive(Eq,
    Debug
    )

    Where NULL and MISSING sort.

    Time

    pub struct Time {
    hour : Int
    minute : Int
    second : Int
    nanos : Int
    precision : Int
    offset : Int?
    } derive(
    Debug
    )

    A time of day. offset is minutes east of UTC; None means WITHOUT TIME ZONE. precision is the number of fractional-second digits (TIME(p)).
    impl Eq for Time
    impl ToValue for Time

    Time::Time

    #alias(new, deprecated="Use Time(...) instead")
    fn Time::Time(hour~ : Int, minute~ : Int, second~ : Int, nanos? : Int, precision? : Int, offset? : Int?) -> Time raise DateTimeError

    Build a time with hours 0..23, minutes and seconds 0..59, and fractional precision 0..9. nanos must fit that precision exactly. offset=None means WITHOUT TIME ZONE; Some(n) is minutes east of UTC in -1439..1439. Raise DateTimeError::InvalidTime for invalid fields. Leap seconds are not accepted.

    Timestamp

    pub struct Timestamp {
    date : Date
    time : Time
    } derive(
    Debug
    )

    A date and a time. The time's offset is the timestamp's offset.
    impl Eq for Timestamp

    Timestamp::Timestamp

    #alias(new, deprecated="Use Timestamp(...) instead")
    fn Timestamp::Timestamp(date : Date, time : Time) -> Timestamp

    Combine an already validated date and time. Preserve the time's precision and optional UTC offset; perform no time zone conversion.

    Tuple

    pub struct Tuple {
    // private fields
    }

    A PartiQL tuple: (name, value) pairs. Order is kept for output only; duplicate names are allowed; no value is MISSING.
    impl Eq for Tuple
    impl ToValue for Tuple

    Tuple::Tuple

    #alias(new, deprecated="Use Tuple(...) instead")
    fn Tuple::Tuple(fields : Array[(String, Value)]) -> Tuple

    Builds a tuple. Pairs whose value is MISSING are dropped.

    test {
    let tuple : @value.Tuple = @value.Tuple([
    ("a", @value.Value::Missing),
    ("b", @value.Value(2)),
    ])
    @expect.expect(tuple.get_all("b")).to_equal([@value.Value(2)])
    }

    Tuple::fields

    fn Tuple::fields(self : Tuple) -> ArrayView[(String, Value)]

    A read-only view of the retained fields, in input order. Duplicate names remain.

    Tuple::get_all

    fn Tuple::get_all(self : Tuple, name : String) -> Array[Value]

    The values of the attributes named name (case-sensitive).

    Tuple::length

    fn Tuple::length(self : Tuple) -> Int

    The number of retained name-value pairs, counting duplicate names.

    Value

    pub(all) enum Value {
    Null
    Missing
    Bool(Bool)
    Int(Int64)
    BigInt(
    BigInt
    )
    Float(Double)
    Decimal(
    Decimal
    )
    String(String)
    Clob(Bytes)
    Blob(Bytes)
    Date(Date)
    Time(Time)
    Timestamp(Timestamp)
    IntervalYM(IntervalYM)
    IntervalDT(IntervalDT)
    List(Array[Value])
    Bag(Bag)
    Tuple(Tuple)
    Map(MapValue)
    Graph(GraphValue)
    Ion(
    Ion
    )
    }

    A PartiQL value.

    Ion holds an exact Ion value (from a backtick literal or $ion::). It is a representation, never a PartiQL type: every semantic acts on its lowered value (see Value::lower). Use Value(x) for ToValue conversions or typed Value::from_* methods for known payload types. Choose from_blob or from_clob explicitly for bytes; retain variants for pattern matching and direct Null/Missing values. Mutable arrays inside list values are shared; copy them when independent storage is required.
    impl Eq for Value
    impl ToValue for Value

    Value::Value

    fn[T : ToValue] Value::Value(value : T) -> Value

    Convert a MoonBit value to its PartiQL representation. Integers use Int when they fit in Int64 and BigInt otherwise. Use Null and Missing directly, and choose from_blob or from_clob explicitly for bytes. Custom domain conversions implement ToValue. Typed from_* methods also supply expected types to nested constructors. Arrays convert recursively to lists. Bag, tuple, map, graph, datetime and interval wrappers keep their kind. Ion values remain boxed, and an existing Value passes through unchanged. Bytes have no implicit conversion. Array conversion allocates a new list; other conversions do not copy nested collection storage.

    test {
    @expect.expect(@value.Value([1, 2])).to_equal(
    @value.Value::from_list([
    @value.Value::from_int(1),
    @value.Value::from_int(2),
    ]),
    )
    }

    Value::fold

    fn[A] Value::fold(self : Value, f : (Value, Array[A]) -> A raise?, prepare? : (Value) -> Value) -> A raise?

    Folds a value tree from leaves to root without using the call stack. prepare runs before descending; f receives the prepared value and its folded children in stored order: list/bag items, tuple values, alternating map keys and values, then graph node/edge payloads. Boxed Ion values are leaves unless prepare lowers them.

    Value::from_bag

    fn Value::from_bag(value : Bag) -> Value

    Wrap an existing bag without rebuilding it; retain duplicates and absent values. The concrete parameter type disambiguates a nested Bag(...) constructor.

    Value::from_bigint

    Normalize an arbitrary-precision integer: use Int when it fits Int64, otherwise BigInt. Equivalent to the legacy Value::integer factory.

    Value::from_blob

    fn Value::from_blob(value : Bytes) -> Value

    Wrap bytes as a binary large object. Distinct from from_clob. The byte storage is shared.

    Value::from_bool

    fn Value::from_bool(value : Bool) -> Value

    Wrap a boolean as a PartiQL boolean.

    Value::from_clob

    fn Value::from_clob(value : Bytes) -> Value

    Wrap bytes as a character large object. Distinct from from_blob. The byte storage is shared; this conversion does not decode text.

    Value::from_date

    fn Value::from_date(value : Date) -> Value

    Wrap a validated Gregorian date without changing its calendar fields.

    Value::from_decimal

    fn Value::from_decimal(value :
    Decimal
    ) -> Value

    Wrap an Ion decimal without changing its coefficient, exponent or scale.

    Value::from_float

    fn Value::from_float(value : Double) -> Value

    Wrap a binary64 number, retaining NaN, infinity and signed zero.

    Value::from_graph

    fn Value::from_graph(value : GraphValue) -> Value

    Wrap a validated graph without rebuilding nodes, edges or payloads.

    Value::from_int

    fn Value::from_int(value : Int64) -> Value

    Wrap an Int64 as a PartiQL integer without changing its value.

    Value::from_interval_dt

    fn Value::from_interval_dt(value : IntervalDT) -> Value

    Wrap a normalized day-time interval without changing its seconds or nanoseconds.

    Value::from_interval_ym

    fn Value::from_interval_ym(value : IntervalYM) -> Value

    Wrap a normalized year-month interval without changing its signed month count.

    Value::from_ion

    fn Value::from_ion(value :
    Ion
    ) -> Value

    Box exact Ion, retaining annotations, symbols and representation details. This does not lower Ion or decode PartiQL annotations; use the Ion codec package's from_ion to decode PartiQL-encoded Ion instead.

    Value::from_list

    fn Value::from_list(value : Array[Value]) -> Value

    Wrap an ordered list, retaining NULL, MISSING and duplicate elements. The backing array is shared. Copy it first if independent storage is required. Unlike generic Value(array), this conversion does not map or copy elements.

    Value::from_map

    fn Value::from_map(value : MapValue) -> Value

    Wrap a validated map without rebuilding entries or changing declared types.

    Value::from_string

    fn Value::from_string(value : String) -> Value

    Wrap Unicode text as a PartiQL string.

    Value::from_time

    fn Value::from_time(value : Time) -> Value

    Wrap a validated time, retaining fractional precision and optional UTC offset.

    Value::from_timestamp

    fn Value::from_timestamp(value : Timestamp) -> Value

    Wrap a validated timestamp, retaining its date, time, precision and offset.

    Value::from_tuple

    fn Value::from_tuple(tuple : Tuple) -> Value

    Wrap an existing tuple as a PartiQL value, preserving its fields and duplicates. This is equivalent to Value(tuple) and does not rebuild the tuple. The concrete parameter type disambiguates a nested Tuple(...) constructor.

    test {
    let value = @value.Value::from_tuple(@value.Tuple([("a", @value.Value(1))]))
    guard value is @value.Value::Tuple(tuple) else {
    fail("expected a tuple value")
    }
    @expect.expect(tuple.get_all("a")).to_equal([@value.Value(1)])
    }

    Value::integer

    An integer value: Int when it fits in Int64, else BigInt.

    Value::is_absent

    fn Value::is_absent(self : Value) -> Bool

    Whether the variant is Null or Missing, without lowering boxed Ion.

    Value::lower

    fn Value::lower(self : Value) -> Value raise LowerError

    Converts a boxed Ion value to its core value, one level deep. Other values are returned unchanged.

    Value::lower_deep

    fn Value::lower_deep(self : Value) -> Value raise LowerError

    Lowers recursively, including the children of collections and tuples.

    compare

    fn compare(a : Value, b : Value, nulls~ : NullOrder) -> Int

    The ORDER BY total order (spec section 4.4). Returns -1, 0 or 1. Values are canonicalized bottom-up, then compared with an explicit stack.

    eqg

    fn eqg(a : Value, b : Value) -> Bool

    Grouping equivalence: NULL eqg NULL, MISSING eqg MISSING, NaN eqg NaN; otherwise the same as =.

    equivalent_by

    fn equivalent_by(a : Value, b : Value, decide : (Value, Value) -> Bool?) -> Bool

    The callback can decide a node comparison after shallow lowering. Returning None uses grouping equivalence for scalars and descends into collections, maps and graph payloads. It must define an equivalence relation, because unordered collections use one-to-one greedy matching.

    group_key

    fn group_key(v : Value) -> String

    A key that values which are eqg share, for hashing (the DISTINCT of SELECT, GROUP BY). It reads two levels into a collection, deeper values by kind and size only, so it takes no deep recursion; values that differ only below that share a key.

    sql_equals

    fn sql_equals(a : Value, b : Value) -> Value

    The PartiQL = operator: MISSING if either side is MISSING, else NULL if either side is NULL, else a Bool. Never fails on mixed types.

    timestamp_of_ion

    Converts an Ion timestamp: missing fields become 0, an unknown offset becomes UTC, and more than 9 fractional digits are truncated.