syntax = "proto3";

package exocore.store;

import "exocore/store/entity.proto";
import "google/protobuf/timestamp.proto";

message EntityQuery {
    // Main search predicate on individual traits of the entity.
    oneof predicate {
        MatchPredicate match = 1;
        TraitPredicate trait = 2;
        IdsPredicate ids = 3;
        ReferencePredicate reference = 4;
        OperationsPredicate operations = 10;
        AllPredicate all = 11;
        BooleanPredicate boolean = 14;
        QueryStringPredicate query_string = 15;

        TestPredicate test = 99;
    }

    // Optional projections on traits and fields to be returned.
    repeated Projection projections = 7;

    // Query paging requested.
    Paging paging = 5;

    // Query ordering.
    Ordering ordering = 6;

    // Optional watch token if this query is to be used for watching.
    uint64 watch_token = 8;

    // If specified, if results from server matches this hash, results will be empty with the
    // `skipped_hash` field set to `true`.
    uint64 result_hash = 9;

    // Include deleted mutations matches. This can be used to return recently modified entities that
    // also include deletions. Deleted traits will be included in the results but will have a
    // `deletion_date` field with the date of the deletion.
    bool include_deleted = 12;

    // Indicates that the query comes from an automated / programmatic logic. 
    // This is used since chain indexation may be deferred until no user queries
    // got received for a while.
    bool programmatic = 13;
}

message Projection {
    // If specified, a prefix match will be done against traits' Protobuf full name (`some.package.Name`).
    // If ends with a dollar sign "$", an exact match is required (ex: `some.package.Name$` will only match this message)
    repeated string package = 1;

    // Skips the trait if the projection matches.
    bool skip = 2;

    // If specified, only return these fields.
    repeated uint32 field_ids = 4;

    // If specified, only return fields annotated with `options.proto`.`field_group_id` matching ids.
    repeated uint32 field_group_ids = 5;
}

// Query entities by text match on all indexed fields across all traits.
message MatchPredicate {
    // Text query.
    string query = 1;

    // Disable fuzzy matching.
    bool no_fuzzy = 2;
}

// Query entities by IDs.
message IdsPredicate {
    repeated string ids = 1;
}

// Query entities by mutations' operation ids.
// Used to return entities on which mutations with these operation ids were applied and indexed.
message OperationsPredicate {
    repeated uint64 operation_ids = 1;
}

// Query all entities.
message AllPredicate {
}

// Used for tests.
message TestPredicate {
    bool success = 1;
}

// Boolean query constructed of different sub-queries with boolean operators.
message BooleanPredicate {
    enum Occur {
        SHOULD = 0;
        MUST = 1;
        MUST_NOT = 2;
    }

    message SubQuery {
        Occur occur = 1;

        oneof predicate {
            MatchPredicate match = 2;
            TraitPredicate trait = 3;
            IdsPredicate ids = 4;
            ReferencePredicate reference = 5;
            OperationsPredicate operations = 6;
            AllPredicate all = 7;
            BooleanPredicate boolean = 8;
        }
    }

    repeated SubQuery queries = 1;
}

// Query entities that have a specified trait and optionally matching a trait query.
message TraitPredicate {
    string trait_name = 1;

    TraitQuery query = 2;
}

message TraitQuery {
    oneof predicate {
        MatchPredicate match = 1;
        TraitFieldPredicate field = 2;
        TraitFieldReferencePredicate reference = 3;
        QueryStringPredicate query_string = 4;
    }
}

message TraitFieldPredicate {
    string field = 1;

    oneof value {
        string string = 2;
        int64 int64 = 3;
        uint64 uint64 = 4;
        google.protobuf.Timestamp date = 5;
    }

    Operator operator = 6;

    enum Operator {
        EQUAL = 0;
        GT = 1;
        GTE = 2;
        LT = 3;
        LTE = 4;
    }
}

message TraitFieldReferencePredicate {
    string field = 1;

    ReferencePredicate reference = 2;
}

message ReferencePredicate {
    // Entity id the reference points to
    string entity_id = 1;

    // Optional trait id the reference points to
    string trait_id = 2;
}

message QueryStringPredicate {
    string query = 1;
}

message Paging {
    // Returns results after this given ordering value.
    OrderingValue after_ordering_value = 1;

    // Returns results before this given ordering value.
    OrderingValue before_ordering_value = 2;

    // Desired results count. Default if 0.
    uint32 count = 3;

    // Mutation index use only, no effect on entity query.
    uint32 offset = 4;
}

message Ordering {
    // Value by which we want results to be ordered.
    oneof value {
        bool score = 1;         // by match score + date boost
        bool operation_id = 2;  // by operation id
        string field = 3;       // by field value
        bool created_at = 7;    // by creation date
        bool updated_at = 8;    // by update date
    }

    // Direction of ordering.
    bool ascending = 4;

    // If match score used, don't boost recent documents
    bool no_recency_boost = 5;

    // If match score used, don't boost results that have references.
    bool no_reference_boost = 6;
}

message OrderingValue {
    // Primary comparison
    oneof value {
        float float = 1;
        uint64 uint64 = 2;
        google.protobuf.Timestamp date = 3;
        bool min = 4;
        bool max = 5;
    }

    // Secondary comparison, in case values were equal. In this case,
    // the last operation id that mutated the entity is used.
    uint64 operation_id = 6;
}

message EntityResults {
    // Entities matching query.
    repeated EntityResult entities = 1;

    // If query specified a `result_hash`, this is set to `true` if the results
    // had the same hash has the specified and that `entities` were set to empty.
    bool skipped_hash = 2;

    // Estimated number of entities matching, based on number of matching mutations.
    uint32 estimated_count = 3;

    // Paging token of the current results.
    Paging current_page = 4;

    // Paging token of the next page of results.
    Paging next_page = 5;

    // Hash of the results. Can be used to prevent receiving same results if they haven't
    // changed by using the `result_hash` field on the query.
    uint64 hash = 6;
}

message EntityResult {
    // The entity with its traits. Projection could have been done on the entity, which will
    // be indicated in its traits' details field.
    Entity entity = 1;

    // Indicates where the entity was taken from in terms of storage. If all of the entities'
    // traits were coming from the chain (i.e. committed), the source will be `CHAIN`. Otherwise,
    // as soon as one entity mutation is coming from pending store (i.e. not committed yet), this
    // field will be `PENDING`.
    //
    // This can be used to know if an entity can be considered stable once mutations were executed on it.
    // Once it's committed, a majority of nodes agreed on it and will not result in further changes happening
    // before the latest consistent timestamp.
    EntityResultSource source = 2;

    // Value to be used to order results. `EntityResults` already contains ordered results,
    // but it may be useful to compare ordering queries (ex.: to merge different pages)
    OrderingValue ordering_value = 3;

    // Hash of the entity result. Can be used to compare if the entity has changed since last results.
    uint64 hash = 4;
}

enum EntityResultSource {
    UNKNOWN = 0;
    PENDING = 1;
    CHAIN = 2;
}
