В процессе реализации вложенных join мы пришли к необходимости поменять форматы сериализации Query и QueryResults, так как иначе запаковать древовидную структуру крайне проблематично.
В v5.16.0 общая схема будет такая (для cproto):
- Клиент может передавать новый caps о поддержке v2-режима
- Сервер может отвечать на этот caps (старый сервер отвечать на него не будет)
- По итогу после login сервер и клиент решают, по какой версии протокола они общаются
Для builtin совместимость не сохраняется - байндинг просто передаёт новый капс. Если капс не передан, то библиотека отказывается делать connect.
Предполагается, что в течение некоторого времени и клиент, и сервер будут поддерживать оба варианта, а затем мигрируют на один общий. Основная цель: незаметная миграция при штатных обновлениях
Новый формат для Query:
Query =
uvarint 2 // V2 only; V1 starts at nsName
· string nsName
· Item* until tag=End // Item tags/payloads same as V1
· uvarint joinCount · JoinRef{joinCount} // V2: counted; V1: JoinRef* until EOF (no counts)
· uvarint mergeCount · JoinRef{mergeCount} // V2: counted; V1: merges are more JoinRefs in the same EOF stream
JoinRef =
uvarint joinType
· Query // V2: full nested Query (own format=2, joins, merges)
// V1: string nsName · Item* until End (no format=2 / no counts);
// further joins spill flat into parent EOF stream
// (after Merge, later joins attach to that merge)
Item =
| Condition: tag0 · string field · uvarint op · CondValues
| Distinct: tag1 · string field // legacy read
| Sort: tag2 · string expr · uvarint desc · uvarint m · variant{m}
| JoinOn: tag3 · uvarint op · uvarint cond · string left · string right
| Limit: tag4 · uvarint n
| Offset: tag5 · uvarint n
| ReqTotal: tag6 · uvarint mode
| DebugLevel: tag7 · uvarint level
| Aggregation: tag8 · uvarint aggType · uvarint n · string{n}
then zero or more of tags {15,16,17} in any order / repeats:
tag17 · string expr · uvarint desc
| tag15 · uvarint limit
| tag16 · uvarint offset
| SelectFilter: tag9 · string field
| SelectFunction: tag10 · string text
| End: tag11 // stops Item*
| Explain: tag12
| EqualPosition: tag13 · uvarint bracketPos · uvarint n · string{n}
| UpdateField: tag14 · string col · uvarint n · (uvarint isExpr · variant){n} // legacy
| OpenBracket: tag18 · uvarint op // nest; closed by tag19
| CloseBracket: tag19
| JoinCondition: tag20 · uvarint joinType · uvarint joinIndex // InnerJoin|OrInnerJoin
| DropField: tag21 · string col
| UpdateObject: tag22 · string col · uvarint n · uvarint isArray · (uvarint isExpr · variant){n}
| WithRank: tag23
| StrictMode: tag24 · uvarint mode
| UpdateFieldV2: tag25 · string col · uvarint isArray · uvarint n · (uvarint isExpr · variant){n}
| BetweenFields: tag26 · uvarint op · string left · uvarint cond · string right
| AlwaysFalse: tag27 · uvarint op
| AlwaysTrue: tag28 · uvarint op
| SubQueryCond: tag29 · uvarint op · string QueryBytes · CondValues
| FieldSubQuery: tag30 · uvarint op · string field · uvarint cond · string QueryBytes
| Local: tag31
| Knn: tag32 · string field · uvarint op · float_vector · KnnParams // legacy
| KnnExt: tag33 · string field · uvarint op · uvarint fmt
· (fmt=0 → float_vector | fmt=1 → string) · KnnParams
| Expressions: tag36 · Expr · uvarint op · uvarint cond · Expr
// tag34, tag35 — deprecated, reject
CondValues = uvarint cond · uvarint n · variant{n} // CondDWithin(11): n=3 as X,Y,distance
KnnParams =
uvarint type // 0=Base, 1=BruteForce, 2=Hnsw, 3=Ivf
· uvarint version // writers emit 1
· uint8 mask
· [uvarint k if mask&1] · [float radius if mask&2]
· [uvarint ef if type=2] · [uvarint nprobe if type=3]
Expr =
uvarint t
· t=0 Field: string name
| t=1 Values: uvarint n · variant{n}
| t=2 Expression: FunctionBlob
| t=3 SubQuery: string QueryBytes
QueryBytes = string whose payload is a full Query // V2 payload includes leading format=2; V1 payload starts at nsName
Новый формат для QueryResults:
QueryResults =
uvarint flags
· uvarint totalCount // TotalCount()
· uvarint qcount // Count()
· uvarint fetchCount // serialized item count (= fetchLimit)
· [ PayloadTypes ]? // if flags & kResultsWithPayloadTypes
· Extra*
· tag End(=0) // ends Extra*
· Item{fetchCount}
PayloadTypes =
uvarint updateCount
· PayloadUpdate{updateCount}
PayloadUpdate =
uvarint nsid
· string nsName
· uvarint stateToken
· uvarint tmVersion
· TagsMatcher // uvarint n · string{n}
· PayloadType // opaque schema blob (field list)
Extra =
| Aggregation: tag1 · slice // JSON or MsgPack per format mask
| Explain: tag2 · slice
| ShardingVersion: tag3 · uvarint ver
| ShardId: tag4 · uvarint commonShardId
// writer then clears WithShardId for items;
// header flags still show the bit — use Extra shardId
| IncarnationTags: tag5 · uvarint shardCount
· (varint shardId · uvarint n · varint{n}){shardCount}
| RankFormat: tag6 · uvarint format // must be 0 = SingleFloatValue
// present ⇒ complex rank (float) on items
Item =
ItemParams
· [ Joined ]? // if flags & kResultsWithJoined
// (cleared for JSON/MsgPack before write)
ItemParams =
[ uvarint id · uvarint lsn ]? // if WithItemID
· [ uvarint nsid ]? // if WithNsID
· [ Rank ]? // if WithRank
· [ uint8 raw · (if raw≠0: slice) ]? // if WithRaw; raw≠0 ⇒ skip shardId + FormatBody
· [ uvarint shardId ]? // if WithShardId and not stopped by raw
// (also absent when common ShardId was in Extra)
· [ FormatBody ]? // omitted when raw≠0
Rank =
float32 // if binding HasComplexRank (often + RankFormat tag6)
| uvarint // legacy uint16 when no complex-rank capability
FormatBody =
| format Json|CJson|MsgPack: slice
| format Pure: ε
| format Ptrs: u64 // server-local; clients reject
Joined =
uvarint fieldsCount // 0 ⇒ no join fields follow
· (if fieldsCount > 0) Field{fieldsCount}
Field =
uvarint itemsCount
· JoinedItem{itemsCount} // empty if itemsCount=0
JoinedItem =
Item // V2: full Item = ItemParams · [Joined] (recursive)
| ItemParams // V1: ItemParams only (one join level, no nested Joined)
В процессе реализации вложенных join мы пришли к необходимости поменять форматы сериализации Query и QueryResults, так как иначе запаковать древовидную структуру крайне проблематично.
В v5.16.0 общая схема будет такая (для cproto):
Для builtin совместимость не сохраняется - байндинг просто передаёт новый капс. Если капс не передан, то библиотека отказывается делать connect.
Предполагается, что в течение некоторого времени и клиент, и сервер будут поддерживать оба варианта, а затем мигрируют на один общий. Основная цель: незаметная миграция при штатных обновлениях
Новый формат для Query:
Новый формат для QueryResults: