@dashevo/dapi-grpc 4.1.1 → 4.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +20 -9
- package/build.rs +35 -4
- package/clients/drive/v0/nodejs/drive_pbjs.js +24409 -12628
- package/clients/platform/v0/nodejs/platform_pbjs.js +24409 -12628
- package/clients/platform/v0/nodejs/platform_protoc.js +22285 -10402
- package/clients/platform/v0/web/platform_pb.d.ts +1603 -61
- package/clients/platform/v0/web/platform_pb.js +22285 -10402
- package/clients/platform/v0/web/platform_pb_service.d.ts +114 -0
- package/clients/platform/v0/web/platform_pb_service.js +240 -0
- package/package.json +2 -2
- package/protos/platform/v0/platform.proto +791 -93
|
@@ -18,6 +18,8 @@ service Platform {
|
|
|
18
18
|
returns (GetIdentityNonceResponse);
|
|
19
19
|
rpc getIdentityContractNonce(GetIdentityContractNonceRequest)
|
|
20
20
|
returns (GetIdentityContractNonceResponse);
|
|
21
|
+
rpc getIdentityKeysRemainingBudgets(GetIdentityKeysRemainingBudgetsRequest)
|
|
22
|
+
returns (GetIdentityKeysRemainingBudgetsResponse);
|
|
21
23
|
rpc getIdentityBalance(GetIdentityBalanceRequest)
|
|
22
24
|
returns (GetIdentityBalanceResponse);
|
|
23
25
|
rpc getIdentitiesBalances(GetIdentitiesBalancesRequest)
|
|
@@ -33,8 +35,18 @@ service Platform {
|
|
|
33
35
|
rpc getDataContract(GetDataContractRequest) returns (GetDataContractResponse);
|
|
34
36
|
rpc getDataContractHistory(GetDataContractHistoryRequest)
|
|
35
37
|
returns (GetDataContractHistoryResponse);
|
|
38
|
+
rpc getDataContractsLatestVersions(GetDataContractsLatestVersionsRequest)
|
|
39
|
+
returns (GetDataContractsLatestVersionsResponse);
|
|
36
40
|
rpc getDataContracts(GetDataContractsRequest)
|
|
37
41
|
returns (GetDataContractsResponse);
|
|
42
|
+
rpc getDataContractsByRange(GetDataContractsByRangeRequest)
|
|
43
|
+
returns (GetDataContractsResponse);
|
|
44
|
+
rpc getContractGroupInfo(GetContractGroupInfoRequest)
|
|
45
|
+
returns (GetContractGroupInfoResponse);
|
|
46
|
+
rpc getContractGroupMembers(GetContractGroupMembersRequest)
|
|
47
|
+
returns (GetContractGroupMembersResponse);
|
|
48
|
+
rpc getContractGroupsForContract(GetContractGroupsForContractRequest)
|
|
49
|
+
returns (GetContractGroupsForContractResponse);
|
|
38
50
|
rpc getDocumentHistory(GetDocumentHistoryRequest)
|
|
39
51
|
returns (GetDocumentHistoryResponse);
|
|
40
52
|
rpc getDocuments(GetDocumentsRequest) returns (GetDocumentsResponse);
|
|
@@ -263,6 +275,40 @@ message GetIdentityContractNonceResponse {
|
|
|
263
275
|
oneof version { GetIdentityContractNonceResponseV0 v0 = 1; }
|
|
264
276
|
}
|
|
265
277
|
|
|
278
|
+
// What is left of the budgets of keys of one identity. A key may carry a
|
|
279
|
+
// total budget (protocol version 14); the key itself never changes, so what
|
|
280
|
+
// remains of it is tracked next to it and read with this query.
|
|
281
|
+
message GetIdentityKeysRemainingBudgetsRequest {
|
|
282
|
+
message GetIdentityKeysRemainingBudgetsRequestV0 {
|
|
283
|
+
bytes identity_id = 1; // ID of the identity
|
|
284
|
+
repeated uint32 key_ids = 2; // IDs of the keys to look up, at least one
|
|
285
|
+
bool prove = 3; // Flag to request a proof as the response
|
|
286
|
+
}
|
|
287
|
+
oneof version { GetIdentityKeysRemainingBudgetsRequestV0 v0 = 1; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
message GetIdentityKeysRemainingBudgetsResponse {
|
|
291
|
+
message GetIdentityKeysRemainingBudgetsResponseV0 {
|
|
292
|
+
message KeyRemainingBudgetEntry {
|
|
293
|
+
uint32 key_id = 1; // ID of the key
|
|
294
|
+
// Credits left of the key's total budget. Absent when the key has no
|
|
295
|
+
// budget (or does not exist). Zero means the key can no longer sign.
|
|
296
|
+
optional uint64 remaining_budget = 2 [ jstype = JS_STRING ];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
message KeysRemainingBudgets {
|
|
300
|
+
repeated KeyRemainingBudgetEntry entries = 1; // One entry per requested key
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
oneof result {
|
|
304
|
+
KeysRemainingBudgets keys_remaining_budgets = 1; // The remaining budgets
|
|
305
|
+
Proof proof = 2; // Proof of the remaining budgets, if requested
|
|
306
|
+
}
|
|
307
|
+
ResponseMetadata metadata = 3; // Metadata about the blockchain state
|
|
308
|
+
}
|
|
309
|
+
oneof version { GetIdentityKeysRemainingBudgetsResponseV0 v0 = 1; }
|
|
310
|
+
}
|
|
311
|
+
|
|
266
312
|
message GetIdentityBalanceResponse {
|
|
267
313
|
|
|
268
314
|
message GetIdentityBalanceResponseV0 {
|
|
@@ -497,6 +543,201 @@ message GetDataContractResponse {
|
|
|
497
543
|
oneof version { GetDataContractResponseV0 v0 = 1; }
|
|
498
544
|
}
|
|
499
545
|
|
|
546
|
+
// Returns the current version number of each requested data contract: the
|
|
547
|
+
// cheap way for a client to check that the contracts it already holds are
|
|
548
|
+
// still current. Every distinct requested id gets an entry; an id no contract has gets
|
|
549
|
+
// an entry without `version`. Serialized contracts are added only when
|
|
550
|
+
// `include_contracts` is set.
|
|
551
|
+
//
|
|
552
|
+
// From protocol version 14 every contract carries a four-byte version item
|
|
553
|
+
// beside it in state. Without `include_contracts`, the unproved form answers
|
|
554
|
+
// from Drive's contract cache or that item without loading a contract, and
|
|
555
|
+
// the proof covers the items, a few hundred bytes of hash path per contract.
|
|
556
|
+
// With `include_contracts`, and on earlier protocol versions, the unproved
|
|
557
|
+
// form reads the contracts through the cache and the proof is the
|
|
558
|
+
// multi-contract proof `getDataContracts` returns, which carries the
|
|
559
|
+
// contracts.
|
|
560
|
+
message GetDataContractsLatestVersionsRequest {
|
|
561
|
+
message GetDataContractsLatestVersionsRequestV0 {
|
|
562
|
+
repeated bytes ids =
|
|
563
|
+
1; // The IDs of the data contracts, at least one and at most 100
|
|
564
|
+
bool include_contracts =
|
|
565
|
+
2; // When set, found entries also carry the serialized contract
|
|
566
|
+
bool prove = 3; // Flag to request a proof as the response
|
|
567
|
+
}
|
|
568
|
+
oneof version { GetDataContractsLatestVersionsRequestV0 v0 = 1; }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
message GetDataContractsLatestVersionsResponse {
|
|
572
|
+
message DataContractLatestVersionEntry {
|
|
573
|
+
bytes identifier = 1; // The requested contract id
|
|
574
|
+
optional uint32 version =
|
|
575
|
+
2; // The contract's current version number; absent when no contract has this id
|
|
576
|
+
optional bytes data_contract =
|
|
577
|
+
3; // The serialized contract, only when `include_contracts` was set and the contract exists
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
message DataContractsLatestVersions {
|
|
581
|
+
repeated DataContractLatestVersionEntry entries =
|
|
582
|
+
1; // One entry per requested contract id
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
message GetDataContractsLatestVersionsResponseV0 {
|
|
586
|
+
oneof result {
|
|
587
|
+
DataContractsLatestVersions data_contracts_latest_versions =
|
|
588
|
+
1; // The current versions, and the contracts if requested
|
|
589
|
+
Proof proof =
|
|
590
|
+
2; // Cryptographic proof of the data contracts, if requested
|
|
591
|
+
}
|
|
592
|
+
ResponseMetadata metadata = 3; // Metadata about the blockchain state
|
|
593
|
+
}
|
|
594
|
+
oneof version { GetDataContractsLatestVersionsResponseV0 v0 = 1; }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// A document type that belongs to a contract group: the contract and the
|
|
598
|
+
// document type name.
|
|
599
|
+
message ContractGroupDocumentTypeMember {
|
|
600
|
+
bytes contract_id = 1; // The contract the document type belongs to
|
|
601
|
+
string document_type_name = 2; // The document type name within the contract
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// A token that belongs to a contract group: the contract and the token's
|
|
605
|
+
// position within it.
|
|
606
|
+
message ContractGroupTokenMember {
|
|
607
|
+
bytes contract_id = 1; // The contract the token belongs to
|
|
608
|
+
uint32 token_position = 2; // The token's position in the contract (u16)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
message GetContractGroupInfoRequest {
|
|
612
|
+
message GetContractGroupInfoRequestV0 {
|
|
613
|
+
bytes contract_group_id = 1; // The 32-byte id of the contract group
|
|
614
|
+
bool prove = 2; // Flag to request a proof as the response
|
|
615
|
+
}
|
|
616
|
+
oneof version { GetContractGroupInfoRequestV0 v0 = 1; }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
message GetContractGroupInfoResponse {
|
|
620
|
+
message ContractGroupInfo {
|
|
621
|
+
bytes owner_id = 1; // The identity that registered the group and owns it
|
|
622
|
+
repeated bytes admin_ids =
|
|
623
|
+
2; // Identities that may add members besides the owner; empty for a
|
|
624
|
+
// single owner
|
|
625
|
+
optional string name = 3; // The group's name, when it has one
|
|
626
|
+
optional string description = 4; // The group's description, when it has one
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
message GetContractGroupInfoResponseV0 {
|
|
630
|
+
oneof result {
|
|
631
|
+
ContractGroupInfo contract_group_info =
|
|
632
|
+
1; // The group's stored information; absent when no such group exists
|
|
633
|
+
Proof proof = 2; // Cryptographic proof of the information, if requested
|
|
634
|
+
}
|
|
635
|
+
ResponseMetadata metadata = 3; // Metadata about the blockchain state
|
|
636
|
+
}
|
|
637
|
+
oneof version { GetContractGroupInfoResponseV0 v0 = 1; }
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
message GetContractGroupMembersRequest {
|
|
641
|
+
// Contracts that belong to the group as a whole, in contract id order.
|
|
642
|
+
message ContractMembersQuery {
|
|
643
|
+
optional bytes start_after =
|
|
644
|
+
1; // 32-byte contract id; the page starts after it
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Document types that belong to the group, in contract id then document
|
|
648
|
+
// type name order.
|
|
649
|
+
message DocumentTypeMembersQuery {
|
|
650
|
+
optional ContractGroupDocumentTypeMember start_after =
|
|
651
|
+
1; // The page starts after this document type
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Tokens that belong to the group, in contract id then token position order.
|
|
655
|
+
message TokenMembersQuery {
|
|
656
|
+
optional ContractGroupTokenMember start_after =
|
|
657
|
+
1; // The page starts after this token
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
message GetContractGroupMembersRequestV0 {
|
|
661
|
+
bytes contract_group_id = 1; // The 32-byte id of the contract group
|
|
662
|
+
oneof members {
|
|
663
|
+
ContractMembersQuery contracts = 2; // Read the member contracts
|
|
664
|
+
DocumentTypeMembersQuery document_types = 3; // Read the member document types
|
|
665
|
+
TokenMembersQuery tokens = 4; // Read the member tokens
|
|
666
|
+
}
|
|
667
|
+
optional uint32 limit =
|
|
668
|
+
5; // Maximum number of members to return, 1..=100; absent means 100
|
|
669
|
+
bool prove = 6; // Flag to request a proof as the response
|
|
670
|
+
}
|
|
671
|
+
oneof version { GetContractGroupMembersRequestV0 v0 = 1; }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
message GetContractGroupMembersResponse {
|
|
675
|
+
message ContractMembers {
|
|
676
|
+
repeated bytes contract_ids = 1; // Member contracts, in contract id order
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
message DocumentTypeMembers {
|
|
680
|
+
repeated ContractGroupDocumentTypeMember document_types =
|
|
681
|
+
1; // Member document types, in contract id then name order
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
message TokenMembers {
|
|
685
|
+
repeated ContractGroupTokenMember tokens =
|
|
686
|
+
1; // Member tokens, in contract id then position order
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
message GetContractGroupMembersResponseV0 {
|
|
690
|
+
oneof result {
|
|
691
|
+
ContractMembers contracts = 1; // One page of member contracts
|
|
692
|
+
DocumentTypeMembers document_types = 2; // One page of member document types
|
|
693
|
+
TokenMembers tokens = 3; // One page of member tokens
|
|
694
|
+
Proof proof = 4; // Cryptographic proof of the page, if requested
|
|
695
|
+
}
|
|
696
|
+
ResponseMetadata metadata = 5; // Metadata about the blockchain state
|
|
697
|
+
}
|
|
698
|
+
oneof version { GetContractGroupMembersResponseV0 v0 = 1; }
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
message GetContractGroupsForContractRequest {
|
|
702
|
+
message GetContractGroupsForContractRequestV0 {
|
|
703
|
+
bytes contract_id = 1; // The 32-byte id of the contract
|
|
704
|
+
bool prove = 2; // Flag to request a proof as the response
|
|
705
|
+
}
|
|
706
|
+
oneof version { GetContractGroupsForContractRequestV0 v0 = 1; }
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
message GetContractGroupsForContractResponse {
|
|
710
|
+
message DocumentTypeMemberships {
|
|
711
|
+
string document_type_name = 1; // The document type within the contract
|
|
712
|
+
repeated bytes contract_group_ids =
|
|
713
|
+
2; // The groups the document type belongs to
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
message TokenMemberships {
|
|
717
|
+
uint32 token_position = 1; // The token's position in the contract (u16)
|
|
718
|
+
repeated bytes contract_group_ids = 2; // The groups the token belongs to
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
message ContractGroupMemberships {
|
|
722
|
+
repeated bytes contract_group_ids =
|
|
723
|
+
1; // The groups the whole contract belongs to
|
|
724
|
+
repeated DocumentTypeMemberships document_types =
|
|
725
|
+
2; // The groups each document type belongs to
|
|
726
|
+
repeated TokenMemberships tokens = 3; // The groups each token belongs to
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
message GetContractGroupsForContractResponseV0 {
|
|
730
|
+
oneof result {
|
|
731
|
+
ContractGroupMemberships contract_group_memberships =
|
|
732
|
+
1; // The memberships; every list is empty when the contract belongs
|
|
733
|
+
// to no group
|
|
734
|
+
Proof proof = 2; // Cryptographic proof of the memberships, if requested
|
|
735
|
+
}
|
|
736
|
+
ResponseMetadata metadata = 3; // Metadata about the blockchain state
|
|
737
|
+
}
|
|
738
|
+
oneof version { GetContractGroupsForContractResponseV0 v0 = 1; }
|
|
739
|
+
}
|
|
740
|
+
|
|
500
741
|
message GetDataContractsRequest {
|
|
501
742
|
message GetDataContractsRequestV0 {
|
|
502
743
|
repeated bytes ids =
|
|
@@ -506,6 +747,28 @@ message GetDataContractsRequest {
|
|
|
506
747
|
oneof version { GetDataContractsRequestV0 v0 = 1; }
|
|
507
748
|
}
|
|
508
749
|
|
|
750
|
+
// Enumerates every data contract on Platform, one page at a time, in
|
|
751
|
+
// ascending contract id order. Answered with `GetDataContractsResponse`:
|
|
752
|
+
// each `DataContractEntry` carries the contract id and, unless `ids_only`
|
|
753
|
+
// is set, the serialized contract. An empty page (no more contracts) is an
|
|
754
|
+
// empty `data_contract_entries` list, never NotFound. A page shorter than
|
|
755
|
+
// `limit` is the last page; otherwise pass the last entry's identifier as
|
|
756
|
+
// `start_after` to fetch the next one.
|
|
757
|
+
message GetDataContractsByRangeRequest {
|
|
758
|
+
message GetDataContractsByRangeRequestV0 {
|
|
759
|
+
optional uint32 limit =
|
|
760
|
+
1; // Maximum number of contracts to return, 1..=100; absent means 100
|
|
761
|
+
oneof start {
|
|
762
|
+
bytes start_after = 2; // 32-byte contract id; the page starts after it
|
|
763
|
+
bytes start_at = 3; // 32-byte contract id; the page starts at it
|
|
764
|
+
}
|
|
765
|
+
bool ids_only =
|
|
766
|
+
4; // When set, entries carry only `identifier`; `data_contract` is unset
|
|
767
|
+
bool prove = 5; // Flag to request a proof as the response
|
|
768
|
+
}
|
|
769
|
+
oneof version { GetDataContractsByRangeRequestV0 v0 = 1; }
|
|
770
|
+
}
|
|
771
|
+
|
|
509
772
|
message GetDataContractsResponse {
|
|
510
773
|
message DataContractEntry {
|
|
511
774
|
bytes identifier = 1; // The unique identifier of the data contract
|
|
@@ -594,6 +857,18 @@ message GetDocumentsRequest {
|
|
|
594
857
|
BETWEEN_EXCLUDE_RIGHT = 8;
|
|
595
858
|
IN = 9;
|
|
596
859
|
STARTS_WITH = 10;
|
|
860
|
+
// Time-range bucket selection (v1 only; the v0 CBOR surface is
|
|
861
|
+
// unaffected). `field` names a timestamp covered by a `timeRange`
|
|
862
|
+
// index. The operand is `WhereClause.time_range` (a
|
|
863
|
+
// `TimeRangeSelection`); `WhereClause.value` must be unset. For the
|
|
864
|
+
// relative selectors (`NEWEST` / `OLDEST`) the server resolves the
|
|
865
|
+
// selection to a bucket-start equality from current block time and
|
|
866
|
+
// the verifier re-derives the same bucket from the quorum-signed
|
|
867
|
+
// metadata time; `BY_START` names the window absolutely, so both
|
|
868
|
+
// sides read the start straight from the query — an ordinary
|
|
869
|
+
// index/count proof either way. See `timeRange` in the document
|
|
870
|
+
// meta-schema and `drive::query::resolve_time_range_bucket_clause`.
|
|
871
|
+
IN_TIME_RANGE = 11;
|
|
597
872
|
}
|
|
598
873
|
|
|
599
874
|
// Tagged scalar (or list) operand for a `WhereClause`. The
|
|
@@ -646,6 +921,54 @@ message GetDocumentsRequest {
|
|
|
646
921
|
}
|
|
647
922
|
}
|
|
648
923
|
|
|
924
|
+
// Operand of an `IN_TIME_RANGE` where clause: which window of a
|
|
925
|
+
// `timeRange` grid the query selects. Typed rather than riding
|
|
926
|
+
// `DocumentFieldValue` — the selection is not a field value, and a
|
|
927
|
+
// structured message keeps the selector an enum instead of a
|
|
928
|
+
// magic string.
|
|
929
|
+
//
|
|
930
|
+
// The relative selectors are resolved server-side: `NEWEST` is the
|
|
931
|
+
// freshest started window (largest grid start <= block time; the
|
|
932
|
+
// latest partial slice of history), `OLDEST` the oldest window still
|
|
933
|
+
// active at block time (a near-full trailing window of ~`range` —
|
|
934
|
+
// best for "trending over the last window"). The proof verifier
|
|
935
|
+
// re-derives the same window from the quorum-signed response
|
|
936
|
+
// metadata time, so neither side trusts the other's clock.
|
|
937
|
+
//
|
|
938
|
+
// `BY_START` names a window absolutely — any window, current or
|
|
939
|
+
// historic — by its start. `start_ms` is then required and must lie
|
|
940
|
+
// on the grid (`start_ms == phase + k * step`, in milliseconds);
|
|
941
|
+
// an unaligned start is rejected rather than snapped. A window with
|
|
942
|
+
// no documents (including one that has not started yet) is a
|
|
943
|
+
// provable empty answer, not an error. The relative selectors must
|
|
944
|
+
// NOT carry `start_ms` — one wire spelling per meaning.
|
|
945
|
+
message TimeRangeSelection {
|
|
946
|
+
enum Selector {
|
|
947
|
+
NEWEST = 0;
|
|
948
|
+
OLDEST = 1;
|
|
949
|
+
BY_START = 2;
|
|
950
|
+
}
|
|
951
|
+
// Names one of the field's declared grids, in the contract's own
|
|
952
|
+
// seconds — verbatim from the contract's `timeRange` declaration.
|
|
953
|
+
// Required when more than one `timeRange` grid buckets the field
|
|
954
|
+
// (the bare selector is ambiguous there and rejected); optional
|
|
955
|
+
// while exactly one grid does. A zero `phase` is the proto3
|
|
956
|
+
// default, matching the contract grammar where `phase` is an
|
|
957
|
+
// omittable key — every grid has exactly one wire spelling by
|
|
958
|
+
// construction.
|
|
959
|
+
message Grid {
|
|
960
|
+
uint64 range = 1 [jstype = JS_STRING];
|
|
961
|
+
uint64 step = 2 [jstype = JS_STRING];
|
|
962
|
+
uint64 phase = 3 [jstype = JS_STRING];
|
|
963
|
+
}
|
|
964
|
+
Selector selector = 1;
|
|
965
|
+
// `BY_START` only: the selected window's start, as a millisecond
|
|
966
|
+
// timestamp on the grid (see the message docstring). Rejected on
|
|
967
|
+
// the relative selectors.
|
|
968
|
+
optional uint64 start_ms = 2 [jstype = JS_STRING];
|
|
969
|
+
Grid grid = 3;
|
|
970
|
+
}
|
|
971
|
+
|
|
649
972
|
// Single `field <op> value` clause. The server reassembles a
|
|
650
973
|
// `Vec<WhereClause>` from the request's `where_clauses` field,
|
|
651
974
|
// runs the same `WhereClause::group_clauses` validator (rejects
|
|
@@ -654,16 +977,26 @@ message GetDocumentsRequest {
|
|
|
654
977
|
// then hands the structured clauses to the executor. Wire
|
|
655
978
|
// semantics are identical to v0's CBOR `[field, op, value]`
|
|
656
979
|
// triples — only the envelope differs.
|
|
980
|
+
//
|
|
981
|
+
// Exactly one operand field is set, keyed by the operator:
|
|
982
|
+
// `operator = IN_TIME_RANGE` carries its operand in `time_range`
|
|
983
|
+
// (`value` must be unset); every other operator carries `value`
|
|
984
|
+
// (`time_range` must be unset). Either mismatch is rejected.
|
|
657
985
|
message WhereClause {
|
|
658
986
|
string field = 1;
|
|
659
987
|
WhereOperator operator = 2;
|
|
660
988
|
DocumentFieldValue value = 3;
|
|
989
|
+
TimeRangeSelection time_range = 4;
|
|
661
990
|
}
|
|
662
991
|
|
|
663
992
|
// Per-group aggregate operand for the left side of a
|
|
664
|
-
// `HavingClause
|
|
665
|
-
// `
|
|
666
|
-
//
|
|
993
|
+
// `HavingClause`, and the aggregate-function target of an
|
|
994
|
+
// `OrderClause`. Only the per-group aggregates live here —
|
|
995
|
+
// `HAVING` is a boolean predicate over one group's own aggregate,
|
|
996
|
+
// and nothing on this message reaches across groups. Cross-group
|
|
997
|
+
// ranking ("which groups score highest?") is expressed with SQL's
|
|
998
|
+
// ordering surface instead: `ORDER BY <the selected aggregate>
|
|
999
|
+
// DESC LIMIT n [OFFSET m]`. See `GetDocumentsRequestV1.order_by`.
|
|
667
1000
|
//
|
|
668
1001
|
// **Field semantics by function**:
|
|
669
1002
|
// - `COUNT`: empty `field` means `COUNT(*)` (group cardinality);
|
|
@@ -682,58 +1015,51 @@ message GetDocumentsRequest {
|
|
|
682
1015
|
string field = 2;
|
|
683
1016
|
}
|
|
684
1017
|
|
|
685
|
-
//
|
|
686
|
-
// `HavingClause`. The ranking is computed over the set of
|
|
687
|
-
// group-aggregate results (one per `GROUP BY` row), so
|
|
688
|
-
// `HAVING COUNT(*) EQ MAX` selects groups whose count equals
|
|
689
|
-
// the maximum count across all groups, and
|
|
690
|
-
// `HAVING COUNT(*) IN TOP(5)` selects groups whose count is
|
|
691
|
-
// among the five largest. Concise way to express top-N /
|
|
692
|
-
// bottom-N selection without window functions or
|
|
693
|
-
// `ORDER BY` + `LIMIT`.
|
|
694
|
-
//
|
|
695
|
-
// **Operator compatibility**:
|
|
696
|
-
// - Scalar operators (`=`, `!=`, `<`, `<=`, `>`, `>=`) work
|
|
697
|
-
// with `MIN` / `MAX`. `TOP` / `BOTTOM` with scalar operators
|
|
698
|
-
// only make sense when `n=1` (the single largest / smallest);
|
|
699
|
-
// evaluation rejects other combinations as ambiguous.
|
|
700
|
-
// - `IN` works with `TOP(n)` / `BOTTOM(n)` for set membership.
|
|
701
|
-
// - `BETWEEN*` doesn't compose meaningfully with rankings and
|
|
702
|
-
// is rejected at evaluation time.
|
|
703
|
-
message HavingRanking {
|
|
704
|
-
enum Kind {
|
|
705
|
-
MIN = 0;
|
|
706
|
-
MAX = 1;
|
|
707
|
-
TOP = 2;
|
|
708
|
-
BOTTOM = 3;
|
|
709
|
-
}
|
|
710
|
-
Kind kind = 1;
|
|
711
|
-
// N-th rank for `TOP` / `BOTTOM` (1-indexed: `n=1` is the
|
|
712
|
-
// single largest / smallest). Required for those two kinds;
|
|
713
|
-
// must be unset for `MIN` / `MAX`. The wire allows setting
|
|
714
|
-
// it on `MIN` / `MAX` for forward compatibility, but
|
|
715
|
-
// evaluation rejects it as a malformed ranking.
|
|
716
|
-
optional uint64 n = 2 [jstype = JS_STRING];
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Single `HAVING <aggregate> <op> <right>` clause. Multiple
|
|
1018
|
+
// Single `HAVING <aggregate> <op> <value>` clause. Multiple
|
|
720
1019
|
// entries in `GetDocumentsRequestV1.having` combine with
|
|
721
1020
|
// implicit AND — same semantics as multiple `where_clauses`
|
|
722
1021
|
// entries. `HAVING COUNT(*) > 5 AND SUM(amount) > 100` is two
|
|
723
1022
|
// `HavingClause` rows, not a tree.
|
|
724
1023
|
//
|
|
1024
|
+
// **`HAVING` is a boolean per-group predicate and nothing else.**
|
|
1025
|
+
// Its right operand is always a literal `DocumentFieldValue`; it
|
|
1026
|
+
// never names another group or the set of groups. Cross-group
|
|
1027
|
+
// ranking — "the 5 highest-scoring groups" — is `ORDER BY <the
|
|
1028
|
+
// selected aggregate> DESC LIMIT 5`, exactly as in SQL, and is
|
|
1029
|
+
// served by the ranked executor (protocol v14+). An earlier draft
|
|
1030
|
+
// of this surface carried the ranking on the right of a `HAVING`
|
|
1031
|
+
// (`HAVING AVG(grade) IN TOP(5)`); that spelling was removed
|
|
1032
|
+
// before release rather than deprecated, because it invented
|
|
1033
|
+
// non-SQL grammar for something SQL already expresses.
|
|
1034
|
+
//
|
|
1035
|
+
// **From protocol v14 a single `HAVING` clause is served as a
|
|
1036
|
+
// bounded range read** (having-range mode): `SELECT <agg> GROUP BY
|
|
1037
|
+
// p HAVING <agg> <op> <value> [ORDER BY <order-key> ASC|DESC]
|
|
1038
|
+
// LIMIT n` answers from the same per-axis secondary as ranked
|
|
1039
|
+
// mode, on an index declaring the matching ranked axis. The
|
|
1040
|
+
// clause's aggregate must be the selected aggregate, the operator
|
|
1041
|
+
// must describe one contiguous range (`NOT_EQUAL` / `IN` are
|
|
1042
|
+
// rejected), and the optional `ORDER BY` picks the walk direction
|
|
1043
|
+
// using the same order-key spelling as ranked mode: `f` for
|
|
1044
|
+
// `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)` —
|
|
1045
|
+
// never an explicit `OrderClause.aggregate` target, which is
|
|
1046
|
+
// rejected. See the supported-shape table on
|
|
1047
|
+
// `GetDocumentsRequestV1`. On protocol v13 and earlier every
|
|
1048
|
+
// non-empty `having` stays rejected with `Unsupported`, exactly as
|
|
1049
|
+
// before.
|
|
1050
|
+
//
|
|
725
1051
|
// The operator set mirrors `WhereOperator` minus `STARTS_WITH`
|
|
726
1052
|
// (prefix matching has no natural meaning against a scalar
|
|
727
1053
|
// aggregate result, even a string-typed one). `BETWEEN*` and
|
|
728
1054
|
// `IN` operand semantics match `WhereOperator`: `BETWEEN*`
|
|
729
1055
|
// expects a 2-element `DocumentFieldValue.list` carrying
|
|
730
1056
|
// `[lower, upper]`, and `IN` expects a `list` of candidate
|
|
731
|
-
// values
|
|
1057
|
+
// values.
|
|
732
1058
|
//
|
|
733
|
-
// The `right` oneof
|
|
734
|
-
// `DocumentFieldValue`
|
|
735
|
-
//
|
|
736
|
-
//
|
|
1059
|
+
// The `right` oneof exists (rather than a bare
|
|
1060
|
+
// `DocumentFieldValue` field) so the "unset right operand" case
|
|
1061
|
+
// stays distinguishable from "the literal null value"; the wire
|
|
1062
|
+
// rejects an unset `right`.
|
|
737
1063
|
message HavingClause {
|
|
738
1064
|
enum Operator {
|
|
739
1065
|
EQUAL = 0;
|
|
@@ -752,33 +1078,65 @@ message GetDocumentsRequest {
|
|
|
752
1078
|
Operator operator = 2;
|
|
753
1079
|
oneof right {
|
|
754
1080
|
DocumentFieldValue value = 3;
|
|
755
|
-
HavingRanking ranking = 4;
|
|
756
1081
|
}
|
|
757
1082
|
}
|
|
758
1083
|
|
|
759
|
-
// Single `ORDER BY
|
|
1084
|
+
// Single `ORDER BY <target> <direction>` clause. Multi-field
|
|
760
1085
|
// ordering is expressed by repeating this message at the
|
|
761
1086
|
// request level (`repeated OrderClause order_by = 4`), matching
|
|
762
1087
|
// SQL's `ORDER BY a ASC, b DESC` shape.
|
|
763
|
-
// Single ORDER BY entry. Multi-entry ordering is expressed by
|
|
764
|
-
// repeating this message at the request level.
|
|
765
1088
|
//
|
|
766
1089
|
// The `target` oneof carries either a plain field name
|
|
767
1090
|
// (`ORDER BY field`) or an aggregate function applied to a
|
|
768
|
-
// field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`)
|
|
769
|
-
// latter sorts per-group result rows produced by `GROUP BY`,
|
|
770
|
-
// useful with `LIMIT` for top-N / bottom-N selection at the
|
|
771
|
-
// routing layer (overlapping `HavingRanking::Top` / `Bottom`
|
|
772
|
-
// but more general because the ranking field can be any
|
|
773
|
-
// aggregate, not just count).
|
|
1091
|
+
// field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`).
|
|
774
1092
|
//
|
|
775
|
-
// **
|
|
776
|
-
//
|
|
777
|
-
//
|
|
778
|
-
//
|
|
1093
|
+
// **Two distinct roles ride the `field` target.**
|
|
1094
|
+
//
|
|
1095
|
+
// 1. *Row ordering* — `select = DOCUMENTS`. `field` names a
|
|
1096
|
+
// document property and the matched rows come back in that
|
|
1097
|
+
// order. This is v0's behaviour, unchanged.
|
|
1098
|
+
//
|
|
1099
|
+
// 2. *Aggregate ordering* — the **ranked** surface (protocol
|
|
1100
|
+
// v14+). With a `GROUP BY` and a single aggregate `select`,
|
|
1101
|
+
// exactly one `order_by` clause naming that select's
|
|
1102
|
+
// aggregate orders the *groups* by their aggregate value and
|
|
1103
|
+
// routes the request to the ranked executor:
|
|
1104
|
+
//
|
|
1105
|
+
// ```text
|
|
1106
|
+
// SELECT AVG(grade) GROUP BY restaurantId
|
|
1107
|
+
// ORDER BY grade DESC LIMIT 3 -- 3 best restaurants
|
|
1108
|
+
// SELECT COUNT(*) GROUP BY restaurantId
|
|
1109
|
+
// ORDER BY $count DESC LIMIT 10 OFFSET 10 -- busiest, page 2
|
|
1110
|
+
// ```
|
|
1111
|
+
//
|
|
1112
|
+
// `SUM(f)` / `AVG(f)` are named by `f` — the same property the
|
|
1113
|
+
// projection aggregates, which is how `ORDER BY avg(grade)`
|
|
1114
|
+
// reads once `SELECT` has already fixed the function.
|
|
1115
|
+
// `COUNT(*)` aggregates no property, so it is named by the
|
|
1116
|
+
// reserved sentinel **`$count`**. The `$` prefix is what keeps
|
|
1117
|
+
// the sentinel from colliding with a real document property:
|
|
1118
|
+
// document properties cannot start with `$` (that namespace is
|
|
1119
|
+
// the system fields' `$id` / `$ownerId` / …), so `$count` can
|
|
1120
|
+
// never be mistaken for a column. `DESC` is the "top n"
|
|
1121
|
+
// reading, `ASC` the "bottom n" reading.
|
|
1122
|
+
//
|
|
1123
|
+
// An `order_by` naming anything other than the selected
|
|
1124
|
+
// aggregate — a second clause, the `GROUP BY` property, an
|
|
1125
|
+
// unrelated field — is rejected rather than normalized: it
|
|
1126
|
+
// asks for an ordering the ranked secondary cannot produce.
|
|
1127
|
+
//
|
|
1128
|
+
// **Aggregate target still rejected** with
|
|
1129
|
+
// `Unsupported("ORDER BY on aggregate keys is not yet
|
|
1130
|
+
// implemented")`. It is the *explicit* spelling of role 2
|
|
1131
|
+
// (`ORDER BY AVG(grade)` rather than `ORDER BY grade` under a
|
|
1132
|
+
// `SELECT AVG(grade)`) and is wire-stable so it can start being
|
|
1133
|
+
// evaluated without another version bump; today the field-target
|
|
1134
|
+
// spelling above is the one the ranked executor reads.
|
|
779
1135
|
message OrderClause {
|
|
780
1136
|
oneof target {
|
|
781
|
-
// Plain field name. Today's evaluated form
|
|
1137
|
+
// Plain field name. Today's evaluated form — a document
|
|
1138
|
+
// property for row ordering, or the selected aggregate's
|
|
1139
|
+
// property (`$count` for `COUNT(*)`) for aggregate ordering.
|
|
782
1140
|
string field = 1;
|
|
783
1141
|
// Aggregate function applied to a field, sorted by the
|
|
784
1142
|
// per-group result. `function = DOCUMENTS` is invalid
|
|
@@ -823,12 +1181,22 @@ message GetDocumentsRequest {
|
|
|
823
1181
|
// other shapes return `Unsupported` (see supported-shape table
|
|
824
1182
|
// below).
|
|
825
1183
|
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
// `
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
1184
|
+
// **Ranked mode** is served from protocol v14 and is selected by
|
|
1185
|
+
// `group_by` + a single `order_by` naming the selected aggregate —
|
|
1186
|
+
// SQL's own top-n spelling, `ORDER BY <agg> DESC LIMIT n OFFSET m`.
|
|
1187
|
+
// It returns `ResultData.ranked`. See `order_by` and the
|
|
1188
|
+
// supported-shape table below.
|
|
1189
|
+
//
|
|
1190
|
+
// **Having-range mode** is served from protocol v14: a single
|
|
1191
|
+
// `having` clause whose aggregate is the selected aggregate turns
|
|
1192
|
+
// the request into a bounded range read over the same per-axis
|
|
1193
|
+
// secondary ranked mode walks, answered in `ResultData.ranked`.
|
|
1194
|
+
// On protocol v13 and earlier every non-empty `having` is rejected
|
|
1195
|
+
// (`"HAVING clause is not yet implemented"`). `having` carries no
|
|
1196
|
+
// ranking spelling: an earlier draft put cross-group ranking on the
|
|
1197
|
+
// right of a `HAVING` (`HAVING AVG(grade) IN TOP(5)`) and that
|
|
1198
|
+
// grammar was removed before release in favour of `ORDER BY` +
|
|
1199
|
+
// `LIMIT`. See the supported-shape table below.
|
|
832
1200
|
//
|
|
833
1201
|
// **Supported shapes** (everything else rejects with a typed
|
|
834
1202
|
// `QuerySyntaxError::Unsupported` so callers can detect un-wired
|
|
@@ -853,8 +1221,17 @@ message GetDocumentsRequest {
|
|
|
853
1221
|
// `select=COUNT, group_by=[a, b]`:
|
|
854
1222
|
// - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value).
|
|
855
1223
|
//
|
|
1224
|
+
// `select=<COUNT(*)|SUM(f)|AVG(f)>, group_by=[p], order_by=[<the selected aggregate>]` (protocol v14+) — **ranked mode**:
|
|
1225
|
+
// - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; a single-element `IN` normalizes to the equality pin; an element whose prefix was never written — at any depth of its pinned chain — contributes an empty branch, union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. A non-zero `offset` is rejected together with `IN` (rank-skip is per-secondary; `OFFSET 0` is the offset-free request).
|
|
1226
|
+
// - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant.
|
|
1227
|
+
//
|
|
1228
|
+
// `select=<COUNT(*)|SUM(f)|AVG(f)>, group_by=[p], having=[<the selected aggregate> <op> <value>]` (protocol v14+) — **having-range mode**:
|
|
1229
|
+
// - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements; a never-written element contributes an empty branch) that fans the bound out across prefix branches and merges, entries carrying `in_key`.
|
|
1230
|
+
// - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie.
|
|
1231
|
+
//
|
|
856
1232
|
// **Rejected shapes** (return `Unsupported`):
|
|
857
|
-
// - any non-empty `having` (
|
|
1233
|
+
// - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN` as the having operator, a `where` shape other than the compound-index prefix pins above — equality pins plus at most one bounded `IN` — or a carried `offset` / cursor).
|
|
1234
|
+
// - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index prefix pins above (an operator other than `EQUAL` or the one permitted `IN`, more than one `IN`, a `null` pin combined with an `IN`, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate.
|
|
858
1235
|
// - `select=DOCUMENTS` with non-empty `group_by`.
|
|
859
1236
|
// - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause.
|
|
860
1237
|
// - `select=COUNT` with `group_by.len() > 2`.
|
|
@@ -927,11 +1304,11 @@ message GetDocumentsRequest {
|
|
|
927
1304
|
AVG = 3;
|
|
928
1305
|
// Per-group MIN / MAX — `SELECT MIN(field) GROUP BY
|
|
929
1306
|
// category` returns the smallest `field` value in each
|
|
930
|
-
// category.
|
|
931
|
-
// `
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
1307
|
+
// category. These operate over the row values *within*
|
|
1308
|
+
// each group, the same way `SUM` and `AVG` do; they are
|
|
1309
|
+
// not a cross-group ranking. To pick out the extreme
|
|
1310
|
+
// *group*, order by the aggregate instead:
|
|
1311
|
+
// `ORDER BY <agg> ASC|DESC LIMIT 1` (see `order_by`).
|
|
935
1312
|
MIN = 4;
|
|
936
1313
|
MAX = 5;
|
|
937
1314
|
}
|
|
@@ -1044,34 +1421,181 @@ message GetDocumentsRequest {
|
|
|
1044
1421
|
// message-level docstring for the supported-shape table.
|
|
1045
1422
|
repeated string group_by = 10;
|
|
1046
1423
|
|
|
1047
|
-
// SQL `HAVING` clauses — aggregate filters that
|
|
1048
|
-
// grouped rows produced by `select
|
|
1049
|
-
// wire shape is `HavingClause`, not
|
|
1050
|
-
// HAVING evaluates against per-group
|
|
1051
|
-
// (`COUNT
|
|
1052
|
-
//
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1424
|
+
// SQL `HAVING` clauses — **boolean** aggregate filters that
|
|
1425
|
+
// apply to the grouped rows produced by `select=<COUNT|SUM|AVG>,
|
|
1426
|
+
// group_by=[…]`. The wire shape is `HavingClause`, not
|
|
1427
|
+
// `WhereClause`, because HAVING evaluates against per-group
|
|
1428
|
+
// aggregates (`COUNT` / `SUM` / `AVG`) rather than row field
|
|
1429
|
+
// values. Multiple entries combine with implicit AND. See
|
|
1430
|
+
// `HavingClause` / `HavingAggregate` for the operator and
|
|
1431
|
+
// aggregate-function catalogs.
|
|
1055
1432
|
//
|
|
1056
|
-
// **
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1061
|
-
// requests
|
|
1062
|
-
//
|
|
1433
|
+
// **From protocol v14 a single clause is served** as a bounded
|
|
1434
|
+
// range read — having-range mode; see the message-level
|
|
1435
|
+
// supported-shape table. On v13 and earlier every non-empty
|
|
1436
|
+
// `having` is rejected with `Unsupported("HAVING clause is not
|
|
1437
|
+
// yet implemented")`. Multi-clause `HAVING COUNT(*) > 5 AND
|
|
1438
|
+
// SUM(amount) > 100` requests can still be constructed on the
|
|
1439
|
+
// wire, but stay rejected until a multi-clause evaluator lands.
|
|
1440
|
+
//
|
|
1441
|
+
// **`having` does not express ranking.** "The n highest-scoring
|
|
1442
|
+
// groups" is `ORDER BY <the selected aggregate> DESC LIMIT n`
|
|
1443
|
+
// (see `order_by`), which *is* served, from protocol v14. An
|
|
1444
|
+
// earlier draft of this surface spelled it
|
|
1445
|
+
// `HAVING AVG(grade) IN TOP(5)`; that grammar was removed
|
|
1446
|
+
// before release rather than deprecated.
|
|
1063
1447
|
repeated HavingClause having = 11;
|
|
1064
1448
|
|
|
1065
1449
|
// Row-based pagination offset, on top of the cursor-based
|
|
1066
1450
|
// `start_after` / `start_at` pagination. `OFFSET N` skips the
|
|
1067
|
-
// first `N`
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
//
|
|
1451
|
+
// first `N` result rows before applying `limit`.
|
|
1452
|
+
//
|
|
1453
|
+
// **Consumed in ranked mode** (protocol v14+): on a request that
|
|
1454
|
+
// routes to the ranked executor (`group_by` + a single `order_by`
|
|
1455
|
+
// naming the selected aggregate), `offset` skips that many ranks
|
|
1456
|
+
// before the returned page, so `ORDER BY avg(grade) DESC LIMIT 1
|
|
1457
|
+
// OFFSET 4` is the 5th-best group. The skip is **counted, not
|
|
1458
|
+
// walked**: grovedb descends on each subtree's aggregate count and
|
|
1459
|
+
// collapses whole subtrees that fit inside the remaining offset, so
|
|
1460
|
+
// the work stays `O(log n + k)` at any offset and the response
|
|
1461
|
+
// reports the skip it performed in `RankedEntries.skipped`. On a
|
|
1462
|
+
// proved request that count is additionally *attested* — committed
|
|
1463
|
+
// to by the proof and re-derived by the verifier; on an unproved
|
|
1464
|
+
// one it is the node's own report. See `RankedEntries.skipped`. There is deliberately no ceiling — an
|
|
1465
|
+
// offset of 4 and an offset of four billion cost the same *order*
|
|
1466
|
+
// of work — neither walks the region it skips — so there is no
|
|
1467
|
+
// denial-of-service lever a cap would close. An offset
|
|
1468
|
+
// past the end of the ranking is a provable answer rather than an
|
|
1469
|
+
// error: `entries` comes back empty and `skipped` is the ranking's
|
|
1470
|
+
// whole population.
|
|
1471
|
+
//
|
|
1472
|
+
// **Rejected everywhere else** with
|
|
1473
|
+
// `Unsupported("OFFSET pagination is not yet implemented")` —
|
|
1474
|
+
// including on every path at protocol v13 and earlier, which has
|
|
1475
|
+
// no ranked executor. Cursor pagination via `start_after` /
|
|
1476
|
+
// `start_at` remains the supported way to page through documents.
|
|
1074
1477
|
optional uint32 offset = 12;
|
|
1478
|
+
|
|
1479
|
+
// Chained mode — a provable semi-join:
|
|
1480
|
+
// `SELECT * FROM <outer_document_type> WHERE $id IN
|
|
1481
|
+
// (SELECT <join_property> FROM <document_type> WHERE ...)`.
|
|
1482
|
+
//
|
|
1483
|
+
// Presence of this message selects chained mode: this request's
|
|
1484
|
+
// own `document_type` / `where_clauses` / `order_by` / `limit`
|
|
1485
|
+
// describe the INNER indexOnly query, and the outer half is
|
|
1486
|
+
// DERIVED from its results — the request carries no outer
|
|
1487
|
+
// clauses by design, and the verifier re-derives the outer
|
|
1488
|
+
// query from the proven inner values, so the join cannot be
|
|
1489
|
+
// steered by the responding node.
|
|
1490
|
+
//
|
|
1491
|
+
// Mode gates (rejected otherwise): the inner type must be
|
|
1492
|
+
// indexOnly and resolve to an index carrying `join_property`;
|
|
1493
|
+
// `join_property` must declare a same-contract
|
|
1494
|
+
// `refersTo: permanentDocument` targeting
|
|
1495
|
+
// `outer_document_type`; `limit` is REQUIRED (it bounds the
|
|
1496
|
+
// derived outer query — no server-default fallback) and capped
|
|
1497
|
+
// by the outer `$id IN` clause's 100-value limit; `selects`
|
|
1498
|
+
// must be empty or a single DOCUMENTS projection; `group_by`,
|
|
1499
|
+
// `having`, time-range clauses, cursors, and `offset` are all
|
|
1500
|
+
// rejected. Pagination is a range clause on `join_property`.
|
|
1501
|
+
//
|
|
1502
|
+
// The verifier needs nothing beyond the proof itself: it
|
|
1503
|
+
// subset-verifies the inner query against the merged proof to
|
|
1504
|
+
// extract the join values, re-derives the outer component, and
|
|
1505
|
+
// verifies the whole composition. A node that predates this
|
|
1506
|
+
// field ignores it (proto3 unknown field) and serves the plain
|
|
1507
|
+
// inner query — which FAILS CLOSED client-side: an inner-only
|
|
1508
|
+
// proof cannot satisfy the re-derived merged query for a
|
|
1509
|
+
// non-empty page, and an unproven response carries the wrong
|
|
1510
|
+
// ResultData variant.
|
|
1511
|
+
message ChainedJoin {
|
|
1512
|
+
// The inner property whose proven values become the outer
|
|
1513
|
+
// documents' `$id`s.
|
|
1514
|
+
string join_property = 1;
|
|
1515
|
+
// The joined document type — the `refersTo` target.
|
|
1516
|
+
string outer_document_type = 2;
|
|
1517
|
+
}
|
|
1518
|
+
ChainedJoin chained = 13;
|
|
1519
|
+
|
|
1520
|
+
// Composite mode — a page plus sub-queries DERIVED from its
|
|
1521
|
+
// results, answered as ONE merged proof over one state root.
|
|
1522
|
+
//
|
|
1523
|
+
// Presence of any `sub_queries` selects composite mode: this
|
|
1524
|
+
// request's own `data_contract_id` / `document_type` /
|
|
1525
|
+
// `where_clauses` / `order_by` / `limit` describe the PAGE, and
|
|
1526
|
+
// every sub-query's `IN` clause is derived by the node from the
|
|
1527
|
+
// page's (or an earlier sub-query's) proven documents. The
|
|
1528
|
+
// verifier re-derives every sub-query from the proven page with
|
|
1529
|
+
// the same builders, re-merges, and verifies the whole
|
|
1530
|
+
// composition — so the composition cannot be steered by the
|
|
1531
|
+
// responding node, and a node that predates this field (proto3
|
|
1532
|
+
// unknown field) serves a page-only proof that FAILS CLOSED
|
|
1533
|
+
// client-side.
|
|
1534
|
+
//
|
|
1535
|
+
// Mode gates (rejected otherwise): `limit` is REQUIRED on the
|
|
1536
|
+
// page (at most 100 — it bounds every derived clause); `selects`
|
|
1537
|
+
// must be empty or a single DOCUMENTS projection; `group_by`,
|
|
1538
|
+
// `having`, time-range clauses, cursors and `offset` are
|
|
1539
|
+
// rejected (paginate with a range clause on the page's ordering
|
|
1540
|
+
// property); `chained` and `sub_queries` are mutually exclusive.
|
|
1541
|
+
// See `SubQuery` for the per-sub-query rules.
|
|
1542
|
+
message SubQuery {
|
|
1543
|
+
// The contract this sub-query targets. Empty = the page's own
|
|
1544
|
+
// contract; otherwise any contract (profiles keyed by owner,
|
|
1545
|
+
// names keyed by identity).
|
|
1546
|
+
bytes data_contract_id = 1;
|
|
1547
|
+
string document_type = 2;
|
|
1548
|
+
// The FIXED clauses — everything but the derived `IN`, which
|
|
1549
|
+
// must not be named here.
|
|
1550
|
+
repeated WhereClause where_clauses = 3;
|
|
1551
|
+
// Ordering (documents only). Every component of the merged proof
|
|
1552
|
+
// walks in the page's direction: a bound field missing from here
|
|
1553
|
+
// is appended in that direction by the node and the verifier
|
|
1554
|
+
// alike, and an ordering that disagrees with the page's direction
|
|
1555
|
+
// is refused (turning a limited lookup around would change the
|
|
1556
|
+
// rows it returns).
|
|
1557
|
+
repeated OrderClause order_by = 4;
|
|
1558
|
+
// Documents lookups on a non-unique index REQUIRE a limit: it
|
|
1559
|
+
// caps the rows the lookup returns in total, in walk order, like
|
|
1560
|
+
// an ordinary IN query's limit (at most 100). Lookups already
|
|
1561
|
+
// bounded by their values (a unique index, or an indexOnly
|
|
1562
|
+
// terminal with every prefix fixed), by-id joins (completeness is
|
|
1563
|
+
// set equality) and counts take none.
|
|
1564
|
+
optional uint32 limit = 5;
|
|
1565
|
+
enum Kind {
|
|
1566
|
+
// The matching documents.
|
|
1567
|
+
DOCUMENTS = 0;
|
|
1568
|
+
// One count per derived value from the `countable` index
|
|
1569
|
+
// covering the fixed clauses plus the bound field. Must be
|
|
1570
|
+
// bound, and must not share its index path with a documents
|
|
1571
|
+
// component (the count reads the value trees the documents
|
|
1572
|
+
// query descends past).
|
|
1573
|
+
COUNT = 1;
|
|
1574
|
+
}
|
|
1575
|
+
Kind kind = 6;
|
|
1576
|
+
// The derived clause `<field> IN <values>`. Absent = a SIBLING:
|
|
1577
|
+
// an independent documents query proven under the same root.
|
|
1578
|
+
message Binding {
|
|
1579
|
+
// Whose proven documents supply the values: `0` = the page,
|
|
1580
|
+
// `n` = `sub_queries[n - 1]` (which must precede this one and
|
|
1581
|
+
// be a DOCUMENTS sub-query).
|
|
1582
|
+
uint32 source = 1;
|
|
1583
|
+
// The source property read off each document: `$id`,
|
|
1584
|
+
// `$ownerId`, or an identifier-typed property (dotted paths
|
|
1585
|
+
// reach nested properties). Documents without it contribute
|
|
1586
|
+
// nothing.
|
|
1587
|
+
string source_property = 2;
|
|
1588
|
+
// The sub-query field receiving the `IN` clause. `$id` makes
|
|
1589
|
+
// this a by-id JOIN: the source property must then declare
|
|
1590
|
+
// `refersTo: permanentDocument` targeting this document type,
|
|
1591
|
+
// so every derived id resolves and a missing document is an
|
|
1592
|
+
// invalid proof. Otherwise `$ownerId` or an indexed property
|
|
1593
|
+
// (a LOOKUP, where absence is a proven fact).
|
|
1594
|
+
string field = 3;
|
|
1595
|
+
}
|
|
1596
|
+
Binding bind = 7;
|
|
1597
|
+
}
|
|
1598
|
+
repeated SubQuery sub_queries = 14;
|
|
1075
1599
|
}
|
|
1076
1600
|
|
|
1077
1601
|
oneof version {
|
|
@@ -1258,9 +1782,134 @@ message GetDocumentsResponse {
|
|
|
1258
1782
|
}
|
|
1259
1783
|
}
|
|
1260
1784
|
|
|
1785
|
+
// One group in a ranked (`GROUP BY … ORDER BY <agg> LIMIT n`)
|
|
1786
|
+
// result: the group's index key plus the aggregate it was ranked
|
|
1787
|
+
// by.
|
|
1788
|
+
//
|
|
1789
|
+
// `key` is the raw index-key bytes of the GROUP BY property's
|
|
1790
|
+
// value — the same bytes that name the group's value tree under
|
|
1791
|
+
// the index (for a `string` property, its UTF-8 bytes). Clients
|
|
1792
|
+
// that want the typed value decode it with the document type's
|
|
1793
|
+
// key deserialization; the wire carries bytes so prover and
|
|
1794
|
+
// verifier agree without a schema round-trip.
|
|
1795
|
+
//
|
|
1796
|
+
// Exactly one `value` variant is set, determined by the
|
|
1797
|
+
// request's SELECT function:
|
|
1798
|
+
// * `count` — `SELECT COUNT(*)`, ranked on the index's
|
|
1799
|
+
// `rankedCountable` axis.
|
|
1800
|
+
// * `sum` — `SELECT SUM(field)`, `rankedSummable` axis.
|
|
1801
|
+
// Signed for the same reason `SumEntry.sum` is.
|
|
1802
|
+
// * `avg` — `SELECT AVG(field)`,
|
|
1803
|
+
// `rankedAverageable` axis.
|
|
1804
|
+
message RankedEntry {
|
|
1805
|
+
bytes key = 1;
|
|
1806
|
+
oneof value {
|
|
1807
|
+
// `jstype = JS_STRING` so JS/Web clients receive a string
|
|
1808
|
+
// and don't round counts > 2^53−1 to the nearest
|
|
1809
|
+
// representable Number — same choice as `CountEntry.count`.
|
|
1810
|
+
uint64 count = 2 [jstype = JS_STRING];
|
|
1811
|
+
// `jstype = JS_STRING` for the same precision reason as
|
|
1812
|
+
// `SumEntry.sum`.
|
|
1813
|
+
sint64 sum = 3 [jstype = JS_STRING];
|
|
1814
|
+
// The group's average, as a **`double` approximation** of the
|
|
1815
|
+
// exact value the Avg axis is ordered by.
|
|
1816
|
+
//
|
|
1817
|
+
// What grovedb actually commits to and sorts by is an `i128`
|
|
1818
|
+
// fixed-point integer: `floor(sum * SCALE / count)` with
|
|
1819
|
+
// euclidean (toward −∞) division, where SCALE is grovedb's
|
|
1820
|
+
// `AVG_FIXED_POINT_SCALE` (currently 10^19). This field is
|
|
1821
|
+
// that integer divided by SCALE in `f64`, i.e.
|
|
1822
|
+
// `fixed_point as f64 / SCALE as f64`.
|
|
1823
|
+
//
|
|
1824
|
+
// A `double` is honest here because `RankedEntry` is only ever
|
|
1825
|
+
// populated on the **no-proof ("quick answer") path**, where
|
|
1826
|
+
// the client has already chosen to trust the server's reply.
|
|
1827
|
+
// A proof-verifying client never reads this field: it
|
|
1828
|
+
// reconstructs each entry from the grovedb proof itself, where
|
|
1829
|
+
// the exact fixed-point `i128` lives, so nothing about proof
|
|
1830
|
+
// verification depends on this number's precision.
|
|
1831
|
+
//
|
|
1832
|
+
// Precision bound: `f64` carries ~15–16 significant decimal
|
|
1833
|
+
// digits, so two groups whose exact fixed-point averages differ
|
|
1834
|
+
// only beyond that can compare equal here. Do not use this
|
|
1835
|
+
// value for equality checks, tie-breaking, or any
|
|
1836
|
+
// reconstruction of the committed integer — request the proof
|
|
1837
|
+
// and read the fixed point from it instead. Entry *order* is
|
|
1838
|
+
// still exact: the server ranks on the i128 before converting.
|
|
1839
|
+
//
|
|
1840
|
+
// SCALE is a grovedb constant, not a wire constant: it moved
|
|
1841
|
+
// from 10^15 to 10^19 before release. Clients that need it
|
|
1842
|
+
// (e.g. to go back to fixed point on the proof path) should
|
|
1843
|
+
// read it from the SDK's re-export (`RANKED_AVG_SCALE`) rather
|
|
1844
|
+
// than hardcoding the literal.
|
|
1845
|
+
double avg = 4;
|
|
1846
|
+
}
|
|
1847
|
+
// The prefix branch this entry came from, set **only** on an
|
|
1848
|
+
// `IN`-pinned request (see the supported-shape table): the
|
|
1849
|
+
// encoded index-key bytes of the `IN` property's pinned value —
|
|
1850
|
+
// empty bytes for the `null` (absent-value) branch. Absent on
|
|
1851
|
+
// single-prefix responses. The same group key can legally appear
|
|
1852
|
+
// under two prefixes, so `(in_key, key)` is the entry's identity
|
|
1853
|
+
// on a merged page, exactly as on `CountEntry`.
|
|
1854
|
+
optional bytes in_key = 5;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// Ranked result entries. **Entry order IS the ranking order** —
|
|
1858
|
+
// best-first for `ORDER BY <agg> DESC`, worst-first for `ASC`.
|
|
1859
|
+
// Clients must not re-sort; ties (equal aggregates) come back in
|
|
1860
|
+
// group-key order in the direction of the walk, which is
|
|
1861
|
+
// descending group-key order for `DESC`.
|
|
1862
|
+
//
|
|
1863
|
+
// Fewer than `limit` entries is normal — the index simply has
|
|
1864
|
+
// fewer groups than requested — and is not an error.
|
|
1865
|
+
message RankedEntries {
|
|
1866
|
+
repeated RankedEntry entries = 1;
|
|
1867
|
+
|
|
1868
|
+
// How many groups were skipped before the first entry — the
|
|
1869
|
+
// page's **starting rank base**. Entry `i` of `entries` is the
|
|
1870
|
+
// group at rank `skipped + i` (0-based).
|
|
1871
|
+
//
|
|
1872
|
+
// `0` for an `OFFSET 0` (or offset-less) query, which is what
|
|
1873
|
+
// makes this field additive: a caller that never paginates sees
|
|
1874
|
+
// the value it would have assumed. For `OFFSET m` it is
|
|
1875
|
+
// normally `m`, and it is what turns a page back into a
|
|
1876
|
+
// *ranking* — without it, a caller who asked for
|
|
1877
|
+
// `ORDER BY avg(grade) DESC LIMIT 1 OFFSET 4` receives one
|
|
1878
|
+
// entry with no way to tell that it really is the 5th-best
|
|
1879
|
+
// group rather than the best.
|
|
1880
|
+
//
|
|
1881
|
+
// **When a requested offset exceeds the population**, `entries`
|
|
1882
|
+
// is empty and `skipped` is the ranking's *total* reported
|
|
1883
|
+
// population — a positive, useful answer ("there are only 12
|
|
1884
|
+
// groups") rather than a bare empty list.
|
|
1885
|
+
//
|
|
1886
|
+
// Both paths report the same quantity: the offset you asked for
|
|
1887
|
+
// when the skip succeeded, and the ranking's total population
|
|
1888
|
+
// when the walk ran out of groups first. They no longer disagree
|
|
1889
|
+
// anywhere, including past the end.
|
|
1890
|
+
//
|
|
1891
|
+
// What differs is the *warrant*, not the value. On the proved
|
|
1892
|
+
// path the number is cryptographically attested — re-derived by
|
|
1893
|
+
// the verifier from the counted subtree commitments in the proof
|
|
1894
|
+
// bytes rather than trusted from this field — so a proving client
|
|
1895
|
+
// should use the verified value and ignore this one. On the
|
|
1896
|
+
// unproven path it is an **unverified claim**, exactly like the
|
|
1897
|
+
// entries beside it: it equals the attested value on an honest
|
|
1898
|
+
// node, and nothing forces a node to be honest. Read "the true
|
|
1899
|
+
// population" as "what this node says the population is".
|
|
1900
|
+
// Callers who need to trust it, rather than merely receive it,
|
|
1901
|
+
// must still prove.
|
|
1902
|
+
//
|
|
1903
|
+
// Do not assume this field equals the offset you requested. It
|
|
1904
|
+
// equals the offset only when the skip succeeded; when the walk
|
|
1905
|
+
// ran out of groups first it is smaller, and that is the answer
|
|
1906
|
+
// rather than an inconsistency.
|
|
1907
|
+
optional uint64 skipped = 2 [jstype = JS_STRING];
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1261
1910
|
// Non-proof result wrapper. The outer `oneof result` switches
|
|
1262
1911
|
// between this and `proof`; this inner oneof switches between
|
|
1263
|
-
// the
|
|
1912
|
+
// the non-proof shapes the v1 surface can return.
|
|
1264
1913
|
message ResultData {
|
|
1265
1914
|
oneof variant {
|
|
1266
1915
|
Documents documents = 1;
|
|
@@ -1280,7 +1929,56 @@ message GetDocumentsResponse {
|
|
|
1280
1929
|
// `book/src/drive/average-index-examples.md` for the design
|
|
1281
1930
|
// and the grades-contract worked example.
|
|
1282
1931
|
AverageResults averages = 4;
|
|
1932
|
+
// Ranked-aggregate result. Routed when the request pairs a
|
|
1933
|
+
// single-property `group_by` with a single `order_by` clause
|
|
1934
|
+
// naming the single aggregate `select` (the `$count` sentinel
|
|
1935
|
+
// for `COUNT(*)`) and a `limit` — SQL's `ORDER BY <agg>
|
|
1936
|
+
// DESC LIMIT n [OFFSET m]`. Answered from the per-axis
|
|
1937
|
+
// secondary of an indexed tree, so the index must declare the
|
|
1938
|
+
// matching `rankedCountable` / `rankedSummable` /
|
|
1939
|
+
// `rankedAverageable` keyword (meta-schema v3, protocol
|
|
1940
|
+
// version 14+). Entry order is the ranking order, and
|
|
1941
|
+
// `skipped` carries the page's starting rank — see
|
|
1942
|
+
// `RankedEntries`.
|
|
1943
|
+
RankedEntries ranked = 5;
|
|
1944
|
+
// Chained-mode result: both halves of the provable
|
|
1945
|
+
// semi-join, in inner order (the last inner projection's
|
|
1946
|
+
// join-property value is the pagination cursor; outer
|
|
1947
|
+
// documents are ordered by first appearance of their id
|
|
1948
|
+
// among the inner projections, deduplicated). Routed when
|
|
1949
|
+
// the request's `chained` message is present.
|
|
1950
|
+
ChainedDocuments chained = 6;
|
|
1951
|
+
// Composite-mode result: the page plus one result per
|
|
1952
|
+
// sub-query, in request order. Routed when the request
|
|
1953
|
+
// carries `sub_queries`.
|
|
1954
|
+
CompositeDocuments composite = 7;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// Both halves of a chained (semi-join) query, each serialized
|
|
1959
|
+
// with its own document type.
|
|
1960
|
+
message ChainedDocuments {
|
|
1961
|
+
repeated bytes inner_documents = 1;
|
|
1962
|
+
repeated bytes outer_documents = 2;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// A composite query's page and per-sub-query results, documents
|
|
1966
|
+
// serialized with their own document type.
|
|
1967
|
+
message CompositeDocuments {
|
|
1968
|
+
// The page, exactly as the page query alone would return it.
|
|
1969
|
+
repeated bytes page_documents = 1;
|
|
1970
|
+
message SubQueryResult {
|
|
1971
|
+
oneof result {
|
|
1972
|
+
// DOCUMENTS: a by-id join in first-appearance order of the
|
|
1973
|
+
// derived ids; a lookup or sibling in query order.
|
|
1974
|
+
Documents documents = 1;
|
|
1975
|
+
// COUNT: one entry per derived value that has a count tree
|
|
1976
|
+
// (a value with no entry counts zero), keyed by the
|
|
1977
|
+
// value's index-key bytes.
|
|
1978
|
+
CountEntries counts = 2;
|
|
1979
|
+
}
|
|
1283
1980
|
}
|
|
1981
|
+
repeated SubQueryResult sub_results = 2;
|
|
1284
1982
|
}
|
|
1285
1983
|
|
|
1286
1984
|
oneof result {
|