You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2021/09/06 07:00:54 UTC

[skywalking-banyandb] branch time-series updated: Finish stream module

This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch time-series
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git


The following commit(s) were added to refs/heads/time-series by this push:
     new 31fb8cf  Finish stream module
31fb8cf is described below

commit 31fb8cf1721b92801f356cd83f68b27a39f7b744
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Mon Sep 6 15:00:01 2021 +0800

    Finish stream module
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
---
 api/common/id.go                                   |   7 +
 api/proto/banyandb/model/v2/common.pb.go           | 239 ++++++++--
 api/proto/banyandb/model/v2/common.proto           |  13 +
 api/proto/banyandb/model/v2/query.pb.go            | 498 ++++++++-------------
 api/proto/banyandb/model/v2/query.proto            |  28 +-
 api/proto/banyandb/model/v2/write.pb.go            | 298 ------------
 api/proto/banyandb/stream/v2/query.pb.go           | 224 +++++----
 api/proto/banyandb/stream/v2/query.proto           |  12 +-
 api/proto/banyandb/stream/v2/write.pb.go           |  78 ++--
 api/proto/banyandb/stream/v2/write.proto           |   6 +-
 banyand/index/tsdb/field_map.go                    |   2 +-
 banyand/kv/badger.go                               | 142 +++++-
 banyand/kv/kv.go                                   |  32 +-
 banyand/storage/database_test.go                   |   4 +-
 banyand/stream/index.go                            |  28 +-
 banyand/stream/service.go                          |  16 +-
 banyand/stream/stream.go                           |  24 +-
 banyand/stream/stream_query.go                     | 117 +++++
 banyand/stream/stream_write.go                     |  66 +--
 banyand/stream/stream_write_test.go                |  23 +-
 banyand/tsdb/block.go                              |  89 +++-
 banyand/tsdb/indexdb.go                            |  44 +-
 banyand/tsdb/series.go                             | 225 ++--------
 banyand/tsdb/series_seek.go                        | 128 ++++++
 banyand/tsdb/series_seek_filter.go                 | 104 +++++
 banyand/tsdb/series_seek_sort.go                   | 200 +++++++++
 banyand/tsdb/series_write.go                       | 177 ++++++++
 banyand/tsdb/seriesdb.go                           |   2 +-
 banyand/tsdb/tsdb.go                               |  18 +-
 pkg/index/index.go                                 |  63 +++
 .../index/tsdb => pkg/index/inverted}/field_map.go |   9 +-
 .../write.proto => pkg/index/inverted/inverted.go  |  45 +-
 pkg/index/inverted/mem.go                          | 155 +++++++
 pkg/index/inverted/mem_test.go                     | 308 +++++++++++++
 pkg/index/inverted/term_map.go                     | 161 +++++++
 .../field_map.go => pkg/index/posting/posting.go   |  73 +--
 pkg/index/posting/roaring/roaring.go               | 214 +++++++++
 pkg/index/search.go                                | 335 ++++++++++++++
 38 files changed, 3098 insertions(+), 1109 deletions(-)

diff --git a/api/common/id.go b/api/common/id.go
index 6eafb8a..5442bcc 100644
--- a/api/common/id.go
+++ b/api/common/id.go
@@ -17,6 +17,13 @@
 
 package common
 
+import "github.com/apache/skywalking-banyandb/pkg/convert"
+
 type ChunkID uint64
 type SeriesID uint64
 type ShardID uint32
+type ItemID uint64
+
+func (s SeriesID) Marshal() []byte {
+	return convert.Uint64ToBytes(uint64(s))
+}
diff --git a/api/proto/banyandb/model/v2/common.pb.go b/api/proto/banyandb/model/v2/common.pb.go
index b6a1499..f848ce7 100644
--- a/api/proto/banyandb/model/v2/common.pb.go
+++ b/api/proto/banyandb/model/v2/common.pb.go
@@ -29,6 +29,7 @@ import (
 
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	structpb "google.golang.org/protobuf/types/known/structpb"
 )
 
 const (
@@ -226,28 +227,185 @@ func (x *IntArray) GetValue() []int64 {
 	return nil
 }
 
+type TagValue struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to Value:
+	//	*TagValue_Null
+	//	*TagValue_Str
+	//	*TagValue_StrArray
+	//	*TagValue_Int
+	//	*TagValue_IntArray
+	//	*TagValue_BinaryData
+	Value isTagValue_Value `protobuf_oneof:"value"`
+}
+
+func (x *TagValue) Reset() {
+	*x = TagValue{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_model_v2_common_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TagValue) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TagValue) ProtoMessage() {}
+
+func (x *TagValue) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_model_v2_common_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TagValue.ProtoReflect.Descriptor instead.
+func (*TagValue) Descriptor() ([]byte, []int) {
+	return file_banyandb_model_v2_common_proto_rawDescGZIP(), []int{4}
+}
+
+func (m *TagValue) GetValue() isTagValue_Value {
+	if m != nil {
+		return m.Value
+	}
+	return nil
+}
+
+func (x *TagValue) GetNull() structpb.NullValue {
+	if x, ok := x.GetValue().(*TagValue_Null); ok {
+		return x.Null
+	}
+	return structpb.NullValue(0)
+}
+
+func (x *TagValue) GetStr() *Str {
+	if x, ok := x.GetValue().(*TagValue_Str); ok {
+		return x.Str
+	}
+	return nil
+}
+
+func (x *TagValue) GetStrArray() *StrArray {
+	if x, ok := x.GetValue().(*TagValue_StrArray); ok {
+		return x.StrArray
+	}
+	return nil
+}
+
+func (x *TagValue) GetInt() *Int {
+	if x, ok := x.GetValue().(*TagValue_Int); ok {
+		return x.Int
+	}
+	return nil
+}
+
+func (x *TagValue) GetIntArray() *IntArray {
+	if x, ok := x.GetValue().(*TagValue_IntArray); ok {
+		return x.IntArray
+	}
+	return nil
+}
+
+func (x *TagValue) GetBinaryData() []byte {
+	if x, ok := x.GetValue().(*TagValue_BinaryData); ok {
+		return x.BinaryData
+	}
+	return nil
+}
+
+type isTagValue_Value interface {
+	isTagValue_Value()
+}
+
+type TagValue_Null struct {
+	Null structpb.NullValue `protobuf:"varint,1,opt,name=null,proto3,enum=google.protobuf.NullValue,oneof"`
+}
+
+type TagValue_Str struct {
+	Str *Str `protobuf:"bytes,2,opt,name=str,proto3,oneof"`
+}
+
+type TagValue_StrArray struct {
+	StrArray *StrArray `protobuf:"bytes,3,opt,name=str_array,json=strArray,proto3,oneof"`
+}
+
+type TagValue_Int struct {
+	Int *Int `protobuf:"bytes,4,opt,name=int,proto3,oneof"`
+}
+
+type TagValue_IntArray struct {
+	IntArray *IntArray `protobuf:"bytes,5,opt,name=int_array,json=intArray,proto3,oneof"`
+}
+
+type TagValue_BinaryData struct {
+	BinaryData []byte `protobuf:"bytes,6,opt,name=binary_data,json=binaryData,proto3,oneof"`
+}
+
+func (*TagValue_Null) isTagValue_Value() {}
+
+func (*TagValue_Str) isTagValue_Value() {}
+
+func (*TagValue_StrArray) isTagValue_Value() {}
+
+func (*TagValue_Int) isTagValue_Value() {}
+
+func (*TagValue_IntArray) isTagValue_Value() {}
+
+func (*TagValue_BinaryData) isTagValue_Value() {}
+
 var File_banyandb_model_v2_common_proto protoreflect.FileDescriptor
 
 var file_banyandb_model_v2_common_proto_rawDesc = []byte{
 	0x0a, 0x1e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
 	0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x12, 0x11, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
-	0x2e, 0x76, 0x32, 0x22, 0x1b, 0x0a, 0x03, 0x53, 0x74, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x22, 0x1b, 0x0a, 0x03, 0x49, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x20, 0x0a,
-	0x08, 0x53, 0x74, 0x72, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
-	0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
-	0x20, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x42, 0x6c, 0x0a, 0x27, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e,
-	0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61,
-	0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x5a, 0x41, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f,
-	0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61,
-	0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61,
-	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2e, 0x76, 0x32, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x22, 0x1b, 0x0a, 0x03, 0x53, 0x74, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1b,
+	0x0a, 0x03, 0x49, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x20, 0x0a, 0x08, 0x53,
+	0x74, 0x72, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x20, 0x0a,
+	0x08, 0x49, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
+	0xb8, 0x02, 0x0a, 0x08, 0x54, 0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x30, 0x0a, 0x04,
+	0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4e, 0x75, 0x6c,
+	0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x75, 0x6c, 0x6c, 0x12, 0x2a,
+	0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61,
+	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e,
+	0x53, 0x74, 0x72, 0x48, 0x00, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x3a, 0x0a, 0x09, 0x73, 0x74,
+	0x72, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
+	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76,
+	0x32, 0x2e, 0x53, 0x74, 0x72, 0x41, 0x72, 0x72, 0x61, 0x79, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74,
+	0x72, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x03, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d,
+	0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x03, 0x69,
+	0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
+	0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x41, 0x72, 0x72,
+	0x61, 0x79, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x21,
+	0x0a, 0x0b, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74,
+	0x61, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x6c, 0x0a, 0x27, 0x6f, 0x72,
+	0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b,
+	0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,
+	0x65, 0x6c, 0x2e, 0x76, 0x32, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b,
+	0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69,
+	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f,
+	0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -262,19 +420,26 @@ func file_banyandb_model_v2_common_proto_rawDescGZIP() []byte {
 	return file_banyandb_model_v2_common_proto_rawDescData
 }
 
-var file_banyandb_model_v2_common_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_banyandb_model_v2_common_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_banyandb_model_v2_common_proto_goTypes = []interface{}{
-	(*Str)(nil),      // 0: banyandb.model.v2.Str
-	(*Int)(nil),      // 1: banyandb.model.v2.Int
-	(*StrArray)(nil), // 2: banyandb.model.v2.StrArray
-	(*IntArray)(nil), // 3: banyandb.model.v2.IntArray
+	(*Str)(nil),             // 0: banyandb.model.v2.Str
+	(*Int)(nil),             // 1: banyandb.model.v2.Int
+	(*StrArray)(nil),        // 2: banyandb.model.v2.StrArray
+	(*IntArray)(nil),        // 3: banyandb.model.v2.IntArray
+	(*TagValue)(nil),        // 4: banyandb.model.v2.TagValue
+	(structpb.NullValue)(0), // 5: google.protobuf.NullValue
 }
 var file_banyandb_model_v2_common_proto_depIdxs = []int32{
-	0, // [0:0] is the sub-list for method output_type
-	0, // [0:0] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
+	5, // 0: banyandb.model.v2.TagValue.null:type_name -> google.protobuf.NullValue
+	0, // 1: banyandb.model.v2.TagValue.str:type_name -> banyandb.model.v2.Str
+	2, // 2: banyandb.model.v2.TagValue.str_array:type_name -> banyandb.model.v2.StrArray
+	1, // 3: banyandb.model.v2.TagValue.int:type_name -> banyandb.model.v2.Int
+	3, // 4: banyandb.model.v2.TagValue.int_array:type_name -> banyandb.model.v2.IntArray
+	5, // [5:5] is the sub-list for method output_type
+	5, // [5:5] is the sub-list for method input_type
+	5, // [5:5] is the sub-list for extension type_name
+	5, // [5:5] is the sub-list for extension extendee
+	0, // [0:5] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_model_v2_common_proto_init() }
@@ -331,6 +496,26 @@ func file_banyandb_model_v2_common_proto_init() {
 				return nil
 			}
 		}
+		file_banyandb_model_v2_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TagValue); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_banyandb_model_v2_common_proto_msgTypes[4].OneofWrappers = []interface{}{
+		(*TagValue_Null)(nil),
+		(*TagValue_Str)(nil),
+		(*TagValue_StrArray)(nil),
+		(*TagValue_Int)(nil),
+		(*TagValue_IntArray)(nil),
+		(*TagValue_BinaryData)(nil),
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -338,7 +523,7 @@ func file_banyandb_model_v2_common_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_banyandb_model_v2_common_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   4,
+			NumMessages:   5,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/proto/banyandb/model/v2/common.proto b/api/proto/banyandb/model/v2/common.proto
index d48f28c..e9c51a5 100644
--- a/api/proto/banyandb/model/v2/common.proto
+++ b/api/proto/banyandb/model/v2/common.proto
@@ -22,6 +22,8 @@ package banyandb.model.v2;
 option java_package = "org.apache.skywalking.banyandb.model.v2";
 option go_package = "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2";
 
+import "google/protobuf/struct.proto";
+
 message Str {
     string value = 1;
 }
@@ -37,3 +39,14 @@ message StrArray {
 message IntArray {
     repeated int64 value = 1;
 }
+
+message TagValue {
+    oneof value {
+        google.protobuf.NullValue null = 1;
+        Str str = 2;
+        StrArray str_array = 3;
+        Int int = 4;
+        IntArray int_array = 5;
+        bytes binary_data = 6;
+    }
+}
diff --git a/api/proto/banyandb/model/v2/query.pb.go b/api/proto/banyandb/model/v2/query.pb.go
index b6cda1c..330251a 100644
--- a/api/proto/banyandb/model/v2/query.pb.go
+++ b/api/proto/banyandb/model/v2/query.pb.go
@@ -30,8 +30,6 @@ import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
-
-	v2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
 )
 
 const (
@@ -45,23 +43,23 @@ const (
 // For EQ, NE, LT, GT, LE and GE, only one operand should be given, i.e. one-to-one relationship.
 // HAVING and NOT_HAVING allow multi-value to be the operand such as array/vector, i.e. one-to-many relationship.
 // For example, "keyA" contains "valueA" **and** "valueB"
-type PairQuery_BinaryOp int32
+type Condition_BinaryOp int32
 
 const (
-	PairQuery_BINARY_OP_UNSPECIFIED PairQuery_BinaryOp = 0
-	PairQuery_BINARY_OP_EQ          PairQuery_BinaryOp = 1
-	PairQuery_BINARY_OP_NE          PairQuery_BinaryOp = 2
-	PairQuery_BINARY_OP_LT          PairQuery_BinaryOp = 3
-	PairQuery_BINARY_OP_GT          PairQuery_BinaryOp = 4
-	PairQuery_BINARY_OP_LE          PairQuery_BinaryOp = 5
-	PairQuery_BINARY_OP_GE          PairQuery_BinaryOp = 6
-	PairQuery_BINARY_OP_HAVING      PairQuery_BinaryOp = 7
-	PairQuery_BINARY_OP_NOT_HAVING  PairQuery_BinaryOp = 8
+	Condition_BINARY_OP_UNSPECIFIED Condition_BinaryOp = 0
+	Condition_BINARY_OP_EQ          Condition_BinaryOp = 1
+	Condition_BINARY_OP_NE          Condition_BinaryOp = 2
+	Condition_BINARY_OP_LT          Condition_BinaryOp = 3
+	Condition_BINARY_OP_GT          Condition_BinaryOp = 4
+	Condition_BINARY_OP_LE          Condition_BinaryOp = 5
+	Condition_BINARY_OP_GE          Condition_BinaryOp = 6
+	Condition_BINARY_OP_HAVING      Condition_BinaryOp = 7
+	Condition_BINARY_OP_NOT_HAVING  Condition_BinaryOp = 8
 )
 
-// Enum value maps for PairQuery_BinaryOp.
+// Enum value maps for Condition_BinaryOp.
 var (
-	PairQuery_BinaryOp_name = map[int32]string{
+	Condition_BinaryOp_name = map[int32]string{
 		0: "BINARY_OP_UNSPECIFIED",
 		1: "BINARY_OP_EQ",
 		2: "BINARY_OP_NE",
@@ -72,7 +70,7 @@ var (
 		7: "BINARY_OP_HAVING",
 		8: "BINARY_OP_NOT_HAVING",
 	}
-	PairQuery_BinaryOp_value = map[string]int32{
+	Condition_BinaryOp_value = map[string]int32{
 		"BINARY_OP_UNSPECIFIED": 0,
 		"BINARY_OP_EQ":          1,
 		"BINARY_OP_NE":          2,
@@ -85,31 +83,31 @@ var (
 	}
 )
 
-func (x PairQuery_BinaryOp) Enum() *PairQuery_BinaryOp {
-	p := new(PairQuery_BinaryOp)
+func (x Condition_BinaryOp) Enum() *Condition_BinaryOp {
+	p := new(Condition_BinaryOp)
 	*p = x
 	return p
 }
 
-func (x PairQuery_BinaryOp) String() string {
+func (x Condition_BinaryOp) String() string {
 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (PairQuery_BinaryOp) Descriptor() protoreflect.EnumDescriptor {
+func (Condition_BinaryOp) Descriptor() protoreflect.EnumDescriptor {
 	return file_banyandb_model_v2_query_proto_enumTypes[0].Descriptor()
 }
 
-func (PairQuery_BinaryOp) Type() protoreflect.EnumType {
+func (Condition_BinaryOp) Type() protoreflect.EnumType {
 	return &file_banyandb_model_v2_query_proto_enumTypes[0]
 }
 
-func (x PairQuery_BinaryOp) Number() protoreflect.EnumNumber {
+func (x Condition_BinaryOp) Number() protoreflect.EnumNumber {
 	return protoreflect.EnumNumber(x)
 }
 
-// Deprecated: Use PairQuery_BinaryOp.Descriptor instead.
-func (PairQuery_BinaryOp) EnumDescriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{1, 0}
+// Deprecated: Use Condition_BinaryOp.Descriptor instead.
+func (Condition_BinaryOp) EnumDescriptor() ([]byte, []int) {
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{2, 0}
 }
 
 type QueryOrder_Sort int32
@@ -158,30 +156,24 @@ func (x QueryOrder_Sort) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use QueryOrder_Sort.Descriptor instead.
 func (QueryOrder_Sort) EnumDescriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{2, 0}
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{3, 0}
 }
 
 // Pair is the building block of a record which is equivalent to a key-value pair.
 // In the context of Trace, it could be metadata of a trace such as service_name, service_instance, etc.
 // Besides, other tags are organized in key-value pair in the underlying storage layer.
 // One should notice that the values can be a multi-value.
-type TypedPair struct {
+type Tag struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
-	// Types that are assignable to Typed:
-	//	*TypedPair_NullPair
-	//	*TypedPair_IntPair
-	//	*TypedPair_StrPair
-	//	*TypedPair_IntArrayPair
-	//	*TypedPair_StrArrayPair
-	Typed isTypedPair_Typed `protobuf_oneof:"typed"`
+	Key   string    `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	Value *TagValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
 }
 
-func (x *TypedPair) Reset() {
-	*x = TypedPair{}
+func (x *Tag) Reset() {
+	*x = Tag{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_banyandb_model_v2_query_proto_msgTypes[0]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -189,13 +181,13 @@ func (x *TypedPair) Reset() {
 	}
 }
 
-func (x *TypedPair) String() string {
+func (x *Tag) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*TypedPair) ProtoMessage() {}
+func (*Tag) ProtoMessage() {}
 
-func (x *TypedPair) ProtoReflect() protoreflect.Message {
+func (x *Tag) ProtoReflect() protoreflect.Message {
 	mi := &file_banyandb_model_v2_query_proto_msgTypes[0]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -207,123 +199,109 @@ func (x *TypedPair) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use TypedPair.ProtoReflect.Descriptor instead.
-func (*TypedPair) Descriptor() ([]byte, []int) {
+// Deprecated: Use Tag.ProtoReflect.Descriptor instead.
+func (*Tag) Descriptor() ([]byte, []int) {
 	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *TypedPair) GetKey() string {
+func (x *Tag) GetKey() string {
 	if x != nil {
 		return x.Key
 	}
 	return ""
 }
 
-func (m *TypedPair) GetTyped() isTypedPair_Typed {
-	if m != nil {
-		return m.Typed
-	}
-	return nil
-}
-
-func (x *TypedPair) GetNullPair() *TypedPair_NullWithType {
-	if x, ok := x.GetTyped().(*TypedPair_NullPair); ok {
-		return x.NullPair
-	}
-	return nil
-}
-
-func (x *TypedPair) GetIntPair() *Int {
-	if x, ok := x.GetTyped().(*TypedPair_IntPair); ok {
-		return x.IntPair
+func (x *Tag) GetValue() *TagValue {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (x *TypedPair) GetStrPair() *Str {
-	if x, ok := x.GetTyped().(*TypedPair_StrPair); ok {
-		return x.StrPair
-	}
-	return nil
-}
+type TagFamily struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
 
-func (x *TypedPair) GetIntArrayPair() *IntArray {
-	if x, ok := x.GetTyped().(*TypedPair_IntArrayPair); ok {
-		return x.IntArrayPair
-	}
-	return nil
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Tags []*Tag `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"`
 }
 
-func (x *TypedPair) GetStrArrayPair() *StrArray {
-	if x, ok := x.GetTyped().(*TypedPair_StrArrayPair); ok {
-		return x.StrArrayPair
+func (x *TagFamily) Reset() {
+	*x = TagFamily{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_model_v2_query_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
 	}
-	return nil
 }
 
-type isTypedPair_Typed interface {
-	isTypedPair_Typed()
+func (x *TagFamily) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-type TypedPair_NullPair struct {
-	NullPair *TypedPair_NullWithType `protobuf:"bytes,2,opt,name=null_pair,json=nullPair,proto3,oneof"`
-}
+func (*TagFamily) ProtoMessage() {}
 
-type TypedPair_IntPair struct {
-	IntPair *Int `protobuf:"bytes,3,opt,name=int_pair,json=intPair,proto3,oneof"`
+func (x *TagFamily) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_model_v2_query_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
 }
 
-type TypedPair_StrPair struct {
-	StrPair *Str `protobuf:"bytes,4,opt,name=str_pair,json=strPair,proto3,oneof"`
+// Deprecated: Use TagFamily.ProtoReflect.Descriptor instead.
+func (*TagFamily) Descriptor() ([]byte, []int) {
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{1}
 }
 
-type TypedPair_IntArrayPair struct {
-	IntArrayPair *IntArray `protobuf:"bytes,5,opt,name=int_array_pair,json=intArrayPair,proto3,oneof"`
+func (x *TagFamily) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
 }
 
-type TypedPair_StrArrayPair struct {
-	StrArrayPair *StrArray `protobuf:"bytes,6,opt,name=str_array_pair,json=strArrayPair,proto3,oneof"`
+func (x *TagFamily) GetTags() []*Tag {
+	if x != nil {
+		return x.Tags
+	}
+	return nil
 }
 
-func (*TypedPair_NullPair) isTypedPair_Typed() {}
-
-func (*TypedPair_IntPair) isTypedPair_Typed() {}
-
-func (*TypedPair_StrPair) isTypedPair_Typed() {}
-
-func (*TypedPair_IntArrayPair) isTypedPair_Typed() {}
-
-func (*TypedPair_StrArrayPair) isTypedPair_Typed() {}
-
-// PairQuery consists of the query condition with a single binary operator to be imposed
+// Condition consists of the query condition with a single binary operator to be imposed
 // For 1:1 BinaryOp, values in condition must be an array with length = 1,
 // while for 1:N BinaryOp, values can be an array with length >= 1.
-type PairQuery struct {
+type Condition struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Op        PairQuery_BinaryOp `protobuf:"varint,1,opt,name=op,proto3,enum=banyandb.model.v2.PairQuery_BinaryOp" json:"op,omitempty"`
-	Condition *TypedPair         `protobuf:"bytes,2,opt,name=condition,proto3" json:"condition,omitempty"`
+	Op    Condition_BinaryOp `protobuf:"varint,1,opt,name=op,proto3,enum=banyandb.model.v2.Condition_BinaryOp" json:"op,omitempty"`
+	Value *TagValue          `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
 }
 
-func (x *PairQuery) Reset() {
-	*x = PairQuery{}
+func (x *Condition) Reset() {
+	*x = Condition{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_query_proto_msgTypes[1]
+		mi := &file_banyandb_model_v2_query_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *PairQuery) String() string {
+func (x *Condition) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*PairQuery) ProtoMessage() {}
+func (*Condition) ProtoMessage() {}
 
-func (x *PairQuery) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_query_proto_msgTypes[1]
+func (x *Condition) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_model_v2_query_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -334,21 +312,21 @@ func (x *PairQuery) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use PairQuery.ProtoReflect.Descriptor instead.
-func (*PairQuery) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{1}
+// Deprecated: Use Condition.ProtoReflect.Descriptor instead.
+func (*Condition) Descriptor() ([]byte, []int) {
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *PairQuery) GetOp() PairQuery_BinaryOp {
+func (x *Condition) GetOp() Condition_BinaryOp {
 	if x != nil {
 		return x.Op
 	}
-	return PairQuery_BINARY_OP_UNSPECIFIED
+	return Condition_BINARY_OP_UNSPECIFIED
 }
 
-func (x *PairQuery) GetCondition() *TypedPair {
+func (x *Condition) GetValue() *TagValue {
 	if x != nil {
-		return x.Condition
+		return x.Value
 	}
 	return nil
 }
@@ -367,7 +345,7 @@ type QueryOrder struct {
 func (x *QueryOrder) Reset() {
 	*x = QueryOrder{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_query_proto_msgTypes[2]
+		mi := &file_banyandb_model_v2_query_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -380,7 +358,7 @@ func (x *QueryOrder) String() string {
 func (*QueryOrder) ProtoMessage() {}
 
 func (x *QueryOrder) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_query_proto_msgTypes[2]
+	mi := &file_banyandb_model_v2_query_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -393,7 +371,7 @@ func (x *QueryOrder) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use QueryOrder.ProtoReflect.Descriptor instead.
 func (*QueryOrder) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{2}
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *QueryOrder) GetKeyName() string {
@@ -416,16 +394,14 @@ type Projection struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// whether binary part is needed
-	DataBinary bool `protobuf:"varint,1,opt,name=data_binary,json=dataBinary,proto3" json:"data_binary,omitempty"`
 	// The key_name refers to the key(s) of Pair(s).
-	KeyNames []string `protobuf:"bytes,2,rep,name=key_names,json=keyNames,proto3" json:"key_names,omitempty"`
+	FagFamilies []*TagFamily `protobuf:"bytes,1,rep,name=fag_families,json=fagFamilies,proto3" json:"fag_families,omitempty"`
 }
 
 func (x *Projection) Reset() {
 	*x = Projection{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_query_proto_msgTypes[3]
+		mi := &file_banyandb_model_v2_query_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -438,7 +414,7 @@ func (x *Projection) String() string {
 func (*Projection) ProtoMessage() {}
 
 func (x *Projection) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_query_proto_msgTypes[3]
+	mi := &file_banyandb_model_v2_query_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -451,19 +427,12 @@ func (x *Projection) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Projection.ProtoReflect.Descriptor instead.
 func (*Projection) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *Projection) GetDataBinary() bool {
-	if x != nil {
-		return x.DataBinary
-	}
-	return false
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{4}
 }
 
-func (x *Projection) GetKeyNames() []string {
+func (x *Projection) GetFagFamilies() []*TagFamily {
 	if x != nil {
-		return x.KeyNames
+		return x.FagFamilies
 	}
 	return nil
 }
@@ -482,7 +451,7 @@ type TimeRange struct {
 func (x *TimeRange) Reset() {
 	*x = TimeRange{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_query_proto_msgTypes[4]
+		mi := &file_banyandb_model_v2_query_proto_msgTypes[5]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -495,7 +464,7 @@ func (x *TimeRange) String() string {
 func (*TimeRange) ProtoMessage() {}
 
 func (x *TimeRange) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_query_proto_msgTypes[4]
+	mi := &file_banyandb_model_v2_query_proto_msgTypes[5]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -508,7 +477,7 @@ func (x *TimeRange) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use TimeRange.ProtoReflect.Descriptor instead.
 func (*TimeRange) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{4}
+	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *TimeRange) GetBegin() *timestamppb.Timestamp {
@@ -525,53 +494,6 @@ func (x *TimeRange) GetEnd() *timestamppb.Timestamp {
 	return nil
 }
 
-type TypedPair_NullWithType struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Type v2.TagType `protobuf:"varint,1,opt,name=type,proto3,enum=banyandb.database.v2.TagType" json:"type,omitempty"`
-}
-
-func (x *TypedPair_NullWithType) Reset() {
-	*x = TypedPair_NullWithType{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_query_proto_msgTypes[5]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *TypedPair_NullWithType) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*TypedPair_NullWithType) ProtoMessage() {}
-
-func (x *TypedPair_NullWithType) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_query_proto_msgTypes[5]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use TypedPair_NullWithType.ProtoReflect.Descriptor instead.
-func (*TypedPair_NullWithType) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_query_proto_rawDescGZIP(), []int{0, 0}
-}
-
-func (x *TypedPair_NullWithType) GetType() v2.TagType {
-	if x != nil {
-		return x.Type
-	}
-	return v2.TagType(0)
-}
-
 var File_banyandb_model_v2_query_proto protoreflect.FileDescriptor
 
 var file_banyandb_model_v2_query_proto_rawDesc = []byte{
@@ -582,85 +504,65 @@ var file_banyandb_model_v2_query_proto_rawDesc = []byte{
 	0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f,
 	0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x64, 0x61,
-	0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, 0x03, 0x0a, 0x09, 0x54, 0x79, 0x70, 0x65, 0x64,
-	0x50, 0x61, 0x69, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x48, 0x0a, 0x09, 0x6e, 0x75, 0x6c, 0x6c, 0x5f, 0x70,
-	0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x62, 0x61, 0x6e, 0x79,
-	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x79,
-	0x70, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x57, 0x69, 0x74, 0x68,
-	0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x50, 0x61, 0x69, 0x72,
-	0x12, 0x33, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f,
-	0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e,
-	0x74, 0x50, 0x61, 0x69, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x5f, 0x70, 0x61, 0x69,
-	0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x48,
-	0x00, 0x52, 0x07, 0x73, 0x74, 0x72, 0x50, 0x61, 0x69, 0x72, 0x12, 0x43, 0x0a, 0x0e, 0x69, 0x6e,
-	0x74, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f,
-	0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x48,
-	0x00, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x50, 0x61, 0x69, 0x72, 0x12,
-	0x43, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x70, 0x61, 0x69,
-	0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x41,
-	0x72, 0x72, 0x61, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x41, 0x72, 0x72, 0x61, 0x79,
-	0x50, 0x61, 0x69, 0x72, 0x1a, 0x41, 0x0a, 0x0c, 0x4e, 0x75, 0x6c, 0x6c, 0x57, 0x69, 0x74, 0x68,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61,
-	0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x61, 0x67, 0x54, 0x79, 0x70,
-	0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x64,
-	0x22, 0xc2, 0x02, 0x0a, 0x09, 0x50, 0x61, 0x69, 0x72, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x35,
-	0x0a, 0x02, 0x6f, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x50,
-	0x61, 0x69, 0x72, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4f,
-	0x70, 0x52, 0x02, 0x6f, 0x70, 0x12, 0x3a, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69,
-	0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61,
-	0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x79, 0x70,
-	0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f,
-	0x6e, 0x22, 0xc1, 0x01, 0x0a, 0x08, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4f, 0x70, 0x12, 0x19,
-	0x0a, 0x15, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x55, 0x4e, 0x53, 0x50,
-	0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e,
-	0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x45, 0x51, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x42,
-	0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a,
-	0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4c, 0x54, 0x10, 0x03, 0x12,
-	0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x47, 0x54, 0x10,
-	0x04, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4c,
-	0x45, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50,
-	0x5f, 0x47, 0x45, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f,
-	0x4f, 0x50, 0x5f, 0x48, 0x41, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x18, 0x0a, 0x14, 0x42,
-	0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x48, 0x41, 0x56,
-	0x49, 0x4e, 0x47, 0x10, 0x08, 0x22, 0x9a, 0x01, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f,
-	0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12,
-	0x36, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e,
-	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76,
-	0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x53, 0x6f, 0x72,
-	0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x22, 0x39, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12,
-	0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-	0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x45,
-	0x53, 0x43, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x41, 0x53, 0x43,
-	0x10, 0x02, 0x22, 0x4a, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x42, 0x69, 0x6e, 0x61, 0x72,
-	0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x6b,
-	0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x62,
-	0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x2c, 0x0a,
-	0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x42, 0x6c, 0x0a, 0x27, 0x6f,
-	0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c,
-	0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f,
-	0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c,
-	0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70,
-	0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x6f, 0x74, 0x6f, 0x22, 0x4a, 0x0a, 0x03, 0x54, 0x61, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61,
+	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e,
+	0x54, 0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
+	0x4b, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x12, 0x2a, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
+	0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,
+	0x76, 0x32, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x22, 0xb9, 0x02, 0x0a,
+	0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x02, 0x6f, 0x70,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4f, 0x70, 0x52, 0x02, 0x6f,
+	0x70, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
+	0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x22, 0xc1, 0x01, 0x0a, 0x08, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4f,
+	0x70, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x55,
+	0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c,
+	0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x45, 0x51, 0x10, 0x01, 0x12, 0x10,
+	0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4e, 0x45, 0x10, 0x02,
+	0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4c, 0x54,
+	0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f,
+	0x47, 0x54, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f,
+	0x50, 0x5f, 0x4c, 0x45, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59,
+	0x5f, 0x4f, 0x50, 0x5f, 0x47, 0x45, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x49, 0x4e, 0x41,
+	0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x48, 0x41, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x18,
+	0x0a, 0x14, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x4f, 0x50, 0x5f, 0x4e, 0x4f, 0x54, 0x5f,
+	0x48, 0x41, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x08, 0x22, 0x9a, 0x01, 0x0a, 0x0a, 0x51, 0x75, 0x65,
+	0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61,
+	0x6d, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x22, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
+	0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x2e,
+	0x53, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x22, 0x39, 0x0a, 0x04, 0x53, 0x6f,
+	0x72, 0x74, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x4f, 0x52, 0x54,
+	0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x4f, 0x52, 0x54, 0x5f,
+	0x41, 0x53, 0x43, 0x10, 0x02, 0x22, 0x4d, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x0c, 0x66, 0x61, 0x67, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c,
+	0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x61,
+	0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x52, 0x0b, 0x66, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69,
+	0x6c, 0x69, 0x65, 0x73, 0x22, 0x6b, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67,
+	0x65, 0x12, 0x30, 0x0a, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x62, 0x65,
+	0x67, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e,
+	0x64, 0x42, 0x6c, 0x0a, 0x27, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e,
+	0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61,
+	0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x5a, 0x41, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f,
+	0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61,
+	0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61,
+	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -678,38 +580,31 @@ func file_banyandb_model_v2_query_proto_rawDescGZIP() []byte {
 var file_banyandb_model_v2_query_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_banyandb_model_v2_query_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_banyandb_model_v2_query_proto_goTypes = []interface{}{
-	(PairQuery_BinaryOp)(0),        // 0: banyandb.model.v2.PairQuery.BinaryOp
-	(QueryOrder_Sort)(0),           // 1: banyandb.model.v2.QueryOrder.Sort
-	(*TypedPair)(nil),              // 2: banyandb.model.v2.TypedPair
-	(*PairQuery)(nil),              // 3: banyandb.model.v2.PairQuery
-	(*QueryOrder)(nil),             // 4: banyandb.model.v2.QueryOrder
-	(*Projection)(nil),             // 5: banyandb.model.v2.Projection
-	(*TimeRange)(nil),              // 6: banyandb.model.v2.TimeRange
-	(*TypedPair_NullWithType)(nil), // 7: banyandb.model.v2.TypedPair.NullWithType
-	(*Int)(nil),                    // 8: banyandb.model.v2.Int
-	(*Str)(nil),                    // 9: banyandb.model.v2.Str
-	(*IntArray)(nil),               // 10: banyandb.model.v2.IntArray
-	(*StrArray)(nil),               // 11: banyandb.model.v2.StrArray
-	(*timestamppb.Timestamp)(nil),  // 12: google.protobuf.Timestamp
-	(v2.TagType)(0),                // 13: banyandb.database.v2.TagType
+	(Condition_BinaryOp)(0),       // 0: banyandb.model.v2.Condition.BinaryOp
+	(QueryOrder_Sort)(0),          // 1: banyandb.model.v2.QueryOrder.Sort
+	(*Tag)(nil),                   // 2: banyandb.model.v2.Tag
+	(*TagFamily)(nil),             // 3: banyandb.model.v2.TagFamily
+	(*Condition)(nil),             // 4: banyandb.model.v2.Condition
+	(*QueryOrder)(nil),            // 5: banyandb.model.v2.QueryOrder
+	(*Projection)(nil),            // 6: banyandb.model.v2.Projection
+	(*TimeRange)(nil),             // 7: banyandb.model.v2.TimeRange
+	(*TagValue)(nil),              // 8: banyandb.model.v2.TagValue
+	(*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
 }
 var file_banyandb_model_v2_query_proto_depIdxs = []int32{
-	7,  // 0: banyandb.model.v2.TypedPair.null_pair:type_name -> banyandb.model.v2.TypedPair.NullWithType
-	8,  // 1: banyandb.model.v2.TypedPair.int_pair:type_name -> banyandb.model.v2.Int
-	9,  // 2: banyandb.model.v2.TypedPair.str_pair:type_name -> banyandb.model.v2.Str
-	10, // 3: banyandb.model.v2.TypedPair.int_array_pair:type_name -> banyandb.model.v2.IntArray
-	11, // 4: banyandb.model.v2.TypedPair.str_array_pair:type_name -> banyandb.model.v2.StrArray
-	0,  // 5: banyandb.model.v2.PairQuery.op:type_name -> banyandb.model.v2.PairQuery.BinaryOp
-	2,  // 6: banyandb.model.v2.PairQuery.condition:type_name -> banyandb.model.v2.TypedPair
-	1,  // 7: banyandb.model.v2.QueryOrder.sort:type_name -> banyandb.model.v2.QueryOrder.Sort
-	12, // 8: banyandb.model.v2.TimeRange.begin:type_name -> google.protobuf.Timestamp
-	12, // 9: banyandb.model.v2.TimeRange.end:type_name -> google.protobuf.Timestamp
-	13, // 10: banyandb.model.v2.TypedPair.NullWithType.type:type_name -> banyandb.database.v2.TagType
-	11, // [11:11] is the sub-list for method output_type
-	11, // [11:11] is the sub-list for method input_type
-	11, // [11:11] is the sub-list for extension type_name
-	11, // [11:11] is the sub-list for extension extendee
-	0,  // [0:11] is the sub-list for field type_name
+	8, // 0: banyandb.model.v2.Tag.value:type_name -> banyandb.model.v2.TagValue
+	2, // 1: banyandb.model.v2.TagFamily.tags:type_name -> banyandb.model.v2.Tag
+	0, // 2: banyandb.model.v2.Condition.op:type_name -> banyandb.model.v2.Condition.BinaryOp
+	8, // 3: banyandb.model.v2.Condition.value:type_name -> banyandb.model.v2.TagValue
+	1, // 4: banyandb.model.v2.QueryOrder.sort:type_name -> banyandb.model.v2.QueryOrder.Sort
+	3, // 5: banyandb.model.v2.Projection.fag_families:type_name -> banyandb.model.v2.TagFamily
+	9, // 6: banyandb.model.v2.TimeRange.begin:type_name -> google.protobuf.Timestamp
+	9, // 7: banyandb.model.v2.TimeRange.end:type_name -> google.protobuf.Timestamp
+	8, // [8:8] is the sub-list for method output_type
+	8, // [8:8] is the sub-list for method input_type
+	8, // [8:8] is the sub-list for extension type_name
+	8, // [8:8] is the sub-list for extension extendee
+	0, // [0:8] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_model_v2_query_proto_init() }
@@ -720,7 +615,7 @@ func file_banyandb_model_v2_query_proto_init() {
 	file_banyandb_model_v2_common_proto_init()
 	if !protoimpl.UnsafeEnabled {
 		file_banyandb_model_v2_query_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TypedPair); i {
+			switch v := v.(*Tag); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -732,7 +627,7 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 		file_banyandb_model_v2_query_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PairQuery); i {
+			switch v := v.(*TagFamily); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -744,7 +639,7 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 		file_banyandb_model_v2_query_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*QueryOrder); i {
+			switch v := v.(*Condition); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -756,7 +651,7 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 		file_banyandb_model_v2_query_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Projection); i {
+			switch v := v.(*QueryOrder); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -768,7 +663,7 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 		file_banyandb_model_v2_query_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TimeRange); i {
+			switch v := v.(*Projection); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -780,7 +675,7 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 		file_banyandb_model_v2_query_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TypedPair_NullWithType); i {
+			switch v := v.(*TimeRange); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -792,13 +687,6 @@ func file_banyandb_model_v2_query_proto_init() {
 			}
 		}
 	}
-	file_banyandb_model_v2_query_proto_msgTypes[0].OneofWrappers = []interface{}{
-		(*TypedPair_NullPair)(nil),
-		(*TypedPair_IntPair)(nil),
-		(*TypedPair_StrPair)(nil),
-		(*TypedPair_IntArrayPair)(nil),
-		(*TypedPair_StrArrayPair)(nil),
-	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
diff --git a/api/proto/banyandb/model/v2/query.proto b/api/proto/banyandb/model/v2/query.proto
index 2d4b7d3..ed39c61 100644
--- a/api/proto/banyandb/model/v2/query.proto
+++ b/api/proto/banyandb/model/v2/query.proto
@@ -24,31 +24,25 @@ package banyandb.model.v2;
 
 import "google/protobuf/timestamp.proto";
 import "banyandb/model/v2/common.proto";
-import "banyandb/database/v2/schema.proto";
 
 // Pair is the building block of a record which is equivalent to a key-value pair.
 // In the context of Trace, it could be metadata of a trace such as service_name, service_instance, etc.
 // Besides, other tags are organized in key-value pair in the underlying storage layer.
 // One should notice that the values can be a multi-value.
-message TypedPair {
+message Tag {
     string key = 1;
-    oneof typed {
-        NullWithType null_pair = 2;
-        Int int_pair = 3;
-        Str str_pair = 4;
-        IntArray int_array_pair = 5;
-        StrArray str_array_pair = 6;
-    }
+    TagValue value = 2;
+}
 
-    message NullWithType {
-        database.v2.TagType type = 1;
-    }
+message TagFamily {
+    string name = 1;
+    repeated Tag tags = 2;
 }
 
-// PairQuery consists of the query condition with a single binary operator to be imposed
+// Condition consists of the query condition with a single binary operator to be imposed
 // For 1:1 BinaryOp, values in condition must be an array with length = 1,
 // while for 1:N BinaryOp, values can be an array with length >= 1.
-message PairQuery {
+message Condition {
     // BinaryOp specifies the operation imposed to the given query condition
     // For EQ, NE, LT, GT, LE and GE, only one operand should be given, i.e. one-to-one relationship.
     // HAVING and NOT_HAVING allow multi-value to be the operand such as array/vector, i.e. one-to-many relationship.
@@ -65,7 +59,7 @@ message PairQuery {
         BINARY_OP_NOT_HAVING = 8;
     }
     BinaryOp op = 1;
-    TypedPair condition = 2;
+    TagValue value = 2;
 }
 
 // QueryOrder means a Sort operation to be done for a given tag.
@@ -82,10 +76,8 @@ message QueryOrder {
 
 // Projection is used to select the names of keys to be returned.
 message Projection {
-    // whether binary part is needed
-    bool data_binary = 1;
     // The key_name refers to the key(s) of Pair(s).
-    repeated string key_names = 2;
+    repeated TagFamily fag_families = 1;
 }
 
 // TimeRange is a range query for uint64,
diff --git a/api/proto/banyandb/model/v2/write.pb.go b/api/proto/banyandb/model/v2/write.pb.go
deleted file mode 100644
index 1640f11..0000000
--- a/api/proto/banyandb/model/v2/write.pb.go
+++ /dev/null
@@ -1,298 +0,0 @@
-// Licensed to Apache Software Foundation (ASF) under one or more contributor
-// license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright
-// ownership. Apache Software Foundation (ASF) licenses this file to you under
-// the Apache License, Version 2.0 (the "License"); you may
-// not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.27.1
-// 	protoc        v3.17.3
-// source: banyandb/model/v2/write.proto
-
-package v2
-
-import (
-	reflect "reflect"
-	sync "sync"
-
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	structpb "google.golang.org/protobuf/types/known/structpb"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type Tag struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Types that are assignable to ValueType:
-	//	*Tag_Null
-	//	*Tag_Str
-	//	*Tag_StrArray
-	//	*Tag_Int
-	//	*Tag_IntArray
-	//	*Tag_BinaryData
-	ValueType isTag_ValueType `protobuf_oneof:"value_type"`
-}
-
-func (x *Tag) Reset() {
-	*x = Tag{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_model_v2_write_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Tag) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Tag) ProtoMessage() {}
-
-func (x *Tag) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_model_v2_write_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Tag.ProtoReflect.Descriptor instead.
-func (*Tag) Descriptor() ([]byte, []int) {
-	return file_banyandb_model_v2_write_proto_rawDescGZIP(), []int{0}
-}
-
-func (m *Tag) GetValueType() isTag_ValueType {
-	if m != nil {
-		return m.ValueType
-	}
-	return nil
-}
-
-func (x *Tag) GetNull() structpb.NullValue {
-	if x, ok := x.GetValueType().(*Tag_Null); ok {
-		return x.Null
-	}
-	return structpb.NullValue(0)
-}
-
-func (x *Tag) GetStr() *Str {
-	if x, ok := x.GetValueType().(*Tag_Str); ok {
-		return x.Str
-	}
-	return nil
-}
-
-func (x *Tag) GetStrArray() *StrArray {
-	if x, ok := x.GetValueType().(*Tag_StrArray); ok {
-		return x.StrArray
-	}
-	return nil
-}
-
-func (x *Tag) GetInt() *Int {
-	if x, ok := x.GetValueType().(*Tag_Int); ok {
-		return x.Int
-	}
-	return nil
-}
-
-func (x *Tag) GetIntArray() *IntArray {
-	if x, ok := x.GetValueType().(*Tag_IntArray); ok {
-		return x.IntArray
-	}
-	return nil
-}
-
-func (x *Tag) GetBinaryData() []byte {
-	if x, ok := x.GetValueType().(*Tag_BinaryData); ok {
-		return x.BinaryData
-	}
-	return nil
-}
-
-type isTag_ValueType interface {
-	isTag_ValueType()
-}
-
-type Tag_Null struct {
-	Null structpb.NullValue `protobuf:"varint,1,opt,name=null,proto3,enum=google.protobuf.NullValue,oneof"`
-}
-
-type Tag_Str struct {
-	Str *Str `protobuf:"bytes,2,opt,name=str,proto3,oneof"`
-}
-
-type Tag_StrArray struct {
-	StrArray *StrArray `protobuf:"bytes,3,opt,name=str_array,json=strArray,proto3,oneof"`
-}
-
-type Tag_Int struct {
-	Int *Int `protobuf:"bytes,4,opt,name=int,proto3,oneof"`
-}
-
-type Tag_IntArray struct {
-	IntArray *IntArray `protobuf:"bytes,5,opt,name=int_array,json=intArray,proto3,oneof"`
-}
-
-type Tag_BinaryData struct {
-	BinaryData []byte `protobuf:"bytes,6,opt,name=binary_data,json=binaryData,proto3,oneof"`
-}
-
-func (*Tag_Null) isTag_ValueType() {}
-
-func (*Tag_Str) isTag_ValueType() {}
-
-func (*Tag_StrArray) isTag_ValueType() {}
-
-func (*Tag_Int) isTag_ValueType() {}
-
-func (*Tag_IntArray) isTag_ValueType() {}
-
-func (*Tag_BinaryData) isTag_ValueType() {}
-
-var File_banyandb_model_v2_write_proto protoreflect.FileDescriptor
-
-var file_banyandb_model_v2_write_proto_rawDesc = []byte{
-	0x0a, 0x1d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
-	0x2f, 0x76, 0x32, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-	0x11, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,
-	0x76, 0x32, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x1a, 0x1e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
-	0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x22, 0xb8, 0x02, 0x0a, 0x03, 0x54, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x04, 0x6e, 0x75, 0x6c, 0x6c,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x56, 0x61, 0x6c,
-	0x75, 0x65, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x75, 0x6c, 0x6c, 0x12, 0x2a, 0x0a, 0x03, 0x73, 0x74,
-	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x48,
-	0x00, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x3a, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x5f, 0x61, 0x72,
-	0x72, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79,
-	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74,
-	0x72, 0x41, 0x72, 0x72, 0x61, 0x79, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74, 0x72, 0x41, 0x72, 0x72,
-	0x61, 0x79, 0x12, 0x2a, 0x0a, 0x03, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
-	0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x03, 0x69, 0x6e, 0x74, 0x12, 0x3a,
-	0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,
-	0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x48, 0x00,
-	0x52, 0x08, 0x69, 0x6e, 0x74, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x69,
-	0x6e, 0x61, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x48,
-	0x00, 0x52, 0x0a, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0c, 0x0a,
-	0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x6c, 0x0a, 0x27, 0x6f,
-	0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c,
-	0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f,
-	0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c,
-	0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70,
-	0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
-}
-
-var (
-	file_banyandb_model_v2_write_proto_rawDescOnce sync.Once
-	file_banyandb_model_v2_write_proto_rawDescData = file_banyandb_model_v2_write_proto_rawDesc
-)
-
-func file_banyandb_model_v2_write_proto_rawDescGZIP() []byte {
-	file_banyandb_model_v2_write_proto_rawDescOnce.Do(func() {
-		file_banyandb_model_v2_write_proto_rawDescData = protoimpl.X.CompressGZIP(file_banyandb_model_v2_write_proto_rawDescData)
-	})
-	return file_banyandb_model_v2_write_proto_rawDescData
-}
-
-var file_banyandb_model_v2_write_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
-var file_banyandb_model_v2_write_proto_goTypes = []interface{}{
-	(*Tag)(nil),             // 0: banyandb.model.v2.Tag
-	(structpb.NullValue)(0), // 1: google.protobuf.NullValue
-	(*Str)(nil),             // 2: banyandb.model.v2.Str
-	(*StrArray)(nil),        // 3: banyandb.model.v2.StrArray
-	(*Int)(nil),             // 4: banyandb.model.v2.Int
-	(*IntArray)(nil),        // 5: banyandb.model.v2.IntArray
-}
-var file_banyandb_model_v2_write_proto_depIdxs = []int32{
-	1, // 0: banyandb.model.v2.Tag.null:type_name -> google.protobuf.NullValue
-	2, // 1: banyandb.model.v2.Tag.str:type_name -> banyandb.model.v2.Str
-	3, // 2: banyandb.model.v2.Tag.str_array:type_name -> banyandb.model.v2.StrArray
-	4, // 3: banyandb.model.v2.Tag.int:type_name -> banyandb.model.v2.Int
-	5, // 4: banyandb.model.v2.Tag.int_array:type_name -> banyandb.model.v2.IntArray
-	5, // [5:5] is the sub-list for method output_type
-	5, // [5:5] is the sub-list for method input_type
-	5, // [5:5] is the sub-list for extension type_name
-	5, // [5:5] is the sub-list for extension extendee
-	0, // [0:5] is the sub-list for field type_name
-}
-
-func init() { file_banyandb_model_v2_write_proto_init() }
-func file_banyandb_model_v2_write_proto_init() {
-	if File_banyandb_model_v2_write_proto != nil {
-		return
-	}
-	file_banyandb_model_v2_common_proto_init()
-	if !protoimpl.UnsafeEnabled {
-		file_banyandb_model_v2_write_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Tag); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	file_banyandb_model_v2_write_proto_msgTypes[0].OneofWrappers = []interface{}{
-		(*Tag_Null)(nil),
-		(*Tag_Str)(nil),
-		(*Tag_StrArray)(nil),
-		(*Tag_Int)(nil),
-		(*Tag_IntArray)(nil),
-		(*Tag_BinaryData)(nil),
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_banyandb_model_v2_write_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   1,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_banyandb_model_v2_write_proto_goTypes,
-		DependencyIndexes: file_banyandb_model_v2_write_proto_depIdxs,
-		MessageInfos:      file_banyandb_model_v2_write_proto_msgTypes,
-	}.Build()
-	File_banyandb_model_v2_write_proto = out.File
-	file_banyandb_model_v2_write_proto_rawDesc = nil
-	file_banyandb_model_v2_write_proto_goTypes = nil
-	file_banyandb_model_v2_write_proto_depIdxs = nil
-}
diff --git a/api/proto/banyandb/stream/v2/query.pb.go b/api/proto/banyandb/stream/v2/query.pb.go
index 9a3d1e1..5ad9ae1 100644
--- a/api/proto/banyandb/stream/v2/query.pb.go
+++ b/api/proto/banyandb/stream/v2/query.pb.go
@@ -56,15 +56,13 @@ type Element struct {
 	// 1) either the start time of a Span/Segment,
 	// 2) or the timestamp of a log
 	Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
-	// data_binary contains all un-indexed Tags and other key-value pairs
-	DataBinary []byte `protobuf:"bytes,3,opt,name=data_binary,json=dataBinary,proto3" json:"data_binary,omitempty"`
 	// fields contains all indexed Field. Some typical names,
 	// - stream_id
 	// - duration
 	// - service_name
 	// - service_instance_id
 	// - end_time_nanoseconds
-	Tags []*v2.TypedPair `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"`
+	TagFamilies []*v2.TagFamily `protobuf:"bytes,3,rep,name=tag_families,json=tagFamilies,proto3" json:"tag_families,omitempty"`
 }
 
 func (x *Element) Reset() {
@@ -113,16 +111,9 @@ func (x *Element) GetTimestamp() *timestamppb.Timestamp {
 	return nil
 }
 
-func (x *Element) GetDataBinary() []byte {
+func (x *Element) GetTagFamilies() []*v2.TagFamily {
 	if x != nil {
-		return x.DataBinary
-	}
-	return nil
-}
-
-func (x *Element) GetTags() []*v2.TypedPair {
-	if x != nil {
-		return x.Tags
+		return x.TagFamilies
 	}
 	return nil
 }
@@ -194,9 +185,8 @@ type QueryRequest struct {
 	// limit is used to impose a boundary on the number of records being returned
 	Limit uint32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"`
 	// order_by is given to specify the sort for a field. So far, only fields in the type of Integer are supported
-	OrderBy *v2.QueryOrder `protobuf:"bytes,5,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"`
-	// tags are indexed. Some typical tags are listed below,
-	Tags []*v2.PairQuery `protobuf:"bytes,6,rep,name=tags,proto3" json:"tags,omitempty"`
+	OrderBy  *v2.QueryOrder           `protobuf:"bytes,5,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"`
+	Criteria []*QueryRequest_Criteria `protobuf:"bytes,6,rep,name=criteria,proto3" json:"criteria,omitempty"`
 	// projection can be used to select the key names of the element in the response
 	Projection *v2.Projection `protobuf:"bytes,7,opt,name=projection,proto3" json:"projection,omitempty"`
 }
@@ -268,9 +258,9 @@ func (x *QueryRequest) GetOrderBy() *v2.QueryOrder {
 	return nil
 }
 
-func (x *QueryRequest) GetTags() []*v2.PairQuery {
+func (x *QueryRequest) GetCriteria() []*QueryRequest_Criteria {
 	if x != nil {
-		return x.Tags
+		return x.Criteria
 	}
 	return nil
 }
@@ -282,6 +272,62 @@ func (x *QueryRequest) GetProjection() *v2.Projection {
 	return nil
 }
 
+// tag_families are indexed.
+type QueryRequest_Criteria struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	TagFamilyName string          `protobuf:"bytes,1,opt,name=tag_family_name,json=tagFamilyName,proto3" json:"tag_family_name,omitempty"`
+	Conditions    []*v2.Condition `protobuf:"bytes,2,rep,name=conditions,proto3" json:"conditions,omitempty"`
+}
+
+func (x *QueryRequest_Criteria) Reset() {
+	*x = QueryRequest_Criteria{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_stream_v2_query_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *QueryRequest_Criteria) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*QueryRequest_Criteria) ProtoMessage() {}
+
+func (x *QueryRequest_Criteria) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_stream_v2_query_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use QueryRequest_Criteria.ProtoReflect.Descriptor instead.
+func (*QueryRequest_Criteria) Descriptor() ([]byte, []int) {
+	return file_banyandb_stream_v2_query_proto_rawDescGZIP(), []int{2, 0}
+}
+
+func (x *QueryRequest_Criteria) GetTagFamilyName() string {
+	if x != nil {
+		return x.TagFamilyName
+	}
+	return ""
+}
+
+func (x *QueryRequest_Criteria) GetConditions() []*v2.Condition {
+	if x != nil {
+		return x.Conditions
+	}
+	return nil
+}
+
 var File_banyandb_stream_v2_query_proto protoreflect.FileDescriptor
 
 var file_banyandb_stream_v2_query_proto_rawDesc = []byte{
@@ -294,52 +340,60 @@ var file_banyandb_stream_v2_query_proto_rawDesc = []byte{
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
 	0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb5, 0x01, 0x0a, 0x07, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa3, 0x01, 0x0a, 0x07, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
 	0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,
 	0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20,
 	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
 	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
-	0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61,
-	0x74, 0x61, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x0a, 0x64, 0x61, 0x74, 0x61, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x74,
-	0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79,
-	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x79,
-	0x70, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x22, 0x48, 0x0a,
-	0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37,
-	0x0a, 0x08, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65,
-	0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x65,
-	0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xde, 0x02, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72,
-	0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e,
-	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-	0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52,
-	0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12,
-	0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
-	0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x38, 0x0a,
-	0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1d, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
-	0x2e, 0x76, 0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x07,
-	0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18,
-	0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x51, 0x75,
-	0x65, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x72, 0x6f,
-	0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
+	0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, 0x0a, 0x0c, 0x74, 0x61,
+	0x67, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
+	0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x52, 0x0b,
+	0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x0d, 0x51,
+	0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08,
+	0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,
+	0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d,
+	0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x65, 0x6c, 0x65,
+	0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xe5, 0x03, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61,
+	0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x3b, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e,
+	0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e,
+	0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a,
+	0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f,
+	0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x38, 0x0a, 0x08, 0x6f,
+	0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
 	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76,
-	0x32, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, 0x72,
-	0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6e, 0x0a, 0x28, 0x6f, 0x72, 0x67, 0x2e,
-	0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e,
-	0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61,
-	0x6d, 0x2e, 0x76, 0x32, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
-	0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69,
-	0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x73,
-	0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x07, 0x6f, 0x72,
+	0x64, 0x65, 0x72, 0x42, 0x79, 0x12, 0x45, 0x0a, 0x08, 0x63, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69,
+	0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
+	0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x51, 0x75, 0x65,
+	0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x65, 0x72,
+	0x69, 0x61, 0x52, 0x08, 0x63, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x12, 0x3d, 0x0a, 0x0a,
+	0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1d, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
+	0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x70, 0x0a, 0x08, 0x43,
+	0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x67, 0x5f, 0x66,
+	0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12,
+	0x3c, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d,
+	0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f,
+	0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x6e, 0x0a,
+	0x28, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77,
+	0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e,
+	0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79,
+	0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
+	0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61,
+	0x6e, 0x64, 0x62, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -354,33 +408,35 @@ func file_banyandb_stream_v2_query_proto_rawDescGZIP() []byte {
 	return file_banyandb_stream_v2_query_proto_rawDescData
 }
 
-var file_banyandb_stream_v2_query_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_banyandb_stream_v2_query_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
 var file_banyandb_stream_v2_query_proto_goTypes = []interface{}{
 	(*Element)(nil),               // 0: banyandb.stream.v2.Element
 	(*QueryResponse)(nil),         // 1: banyandb.stream.v2.QueryResponse
 	(*QueryRequest)(nil),          // 2: banyandb.stream.v2.QueryRequest
-	(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
-	(*v2.TypedPair)(nil),          // 4: banyandb.model.v2.TypedPair
-	(*v21.Metadata)(nil),          // 5: banyandb.common.v2.Metadata
-	(*v2.TimeRange)(nil),          // 6: banyandb.model.v2.TimeRange
-	(*v2.QueryOrder)(nil),         // 7: banyandb.model.v2.QueryOrder
-	(*v2.PairQuery)(nil),          // 8: banyandb.model.v2.PairQuery
+	(*QueryRequest_Criteria)(nil), // 3: banyandb.stream.v2.QueryRequest.Criteria
+	(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
+	(*v2.TagFamily)(nil),          // 5: banyandb.model.v2.TagFamily
+	(*v21.Metadata)(nil),          // 6: banyandb.common.v2.Metadata
+	(*v2.TimeRange)(nil),          // 7: banyandb.model.v2.TimeRange
+	(*v2.QueryOrder)(nil),         // 8: banyandb.model.v2.QueryOrder
 	(*v2.Projection)(nil),         // 9: banyandb.model.v2.Projection
+	(*v2.Condition)(nil),          // 10: banyandb.model.v2.Condition
 }
 var file_banyandb_stream_v2_query_proto_depIdxs = []int32{
-	3, // 0: banyandb.stream.v2.Element.timestamp:type_name -> google.protobuf.Timestamp
-	4, // 1: banyandb.stream.v2.Element.tags:type_name -> banyandb.model.v2.TypedPair
-	0, // 2: banyandb.stream.v2.QueryResponse.elements:type_name -> banyandb.stream.v2.Element
-	5, // 3: banyandb.stream.v2.QueryRequest.metadata:type_name -> banyandb.common.v2.Metadata
-	6, // 4: banyandb.stream.v2.QueryRequest.time_range:type_name -> banyandb.model.v2.TimeRange
-	7, // 5: banyandb.stream.v2.QueryRequest.order_by:type_name -> banyandb.model.v2.QueryOrder
-	8, // 6: banyandb.stream.v2.QueryRequest.tags:type_name -> banyandb.model.v2.PairQuery
-	9, // 7: banyandb.stream.v2.QueryRequest.projection:type_name -> banyandb.model.v2.Projection
-	8, // [8:8] is the sub-list for method output_type
-	8, // [8:8] is the sub-list for method input_type
-	8, // [8:8] is the sub-list for extension type_name
-	8, // [8:8] is the sub-list for extension extendee
-	0, // [0:8] is the sub-list for field type_name
+	4,  // 0: banyandb.stream.v2.Element.timestamp:type_name -> google.protobuf.Timestamp
+	5,  // 1: banyandb.stream.v2.Element.tag_families:type_name -> banyandb.model.v2.TagFamily
+	0,  // 2: banyandb.stream.v2.QueryResponse.elements:type_name -> banyandb.stream.v2.Element
+	6,  // 3: banyandb.stream.v2.QueryRequest.metadata:type_name -> banyandb.common.v2.Metadata
+	7,  // 4: banyandb.stream.v2.QueryRequest.time_range:type_name -> banyandb.model.v2.TimeRange
+	8,  // 5: banyandb.stream.v2.QueryRequest.order_by:type_name -> banyandb.model.v2.QueryOrder
+	3,  // 6: banyandb.stream.v2.QueryRequest.criteria:type_name -> banyandb.stream.v2.QueryRequest.Criteria
+	9,  // 7: banyandb.stream.v2.QueryRequest.projection:type_name -> banyandb.model.v2.Projection
+	10, // 8: banyandb.stream.v2.QueryRequest.Criteria.conditions:type_name -> banyandb.model.v2.Condition
+	9,  // [9:9] is the sub-list for method output_type
+	9,  // [9:9] is the sub-list for method input_type
+	9,  // [9:9] is the sub-list for extension type_name
+	9,  // [9:9] is the sub-list for extension extendee
+	0,  // [0:9] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_stream_v2_query_proto_init() }
@@ -425,6 +481,18 @@ func file_banyandb_stream_v2_query_proto_init() {
 				return nil
 			}
 		}
+		file_banyandb_stream_v2_query_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*QueryRequest_Criteria); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -432,7 +500,7 @@ func file_banyandb_stream_v2_query_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_banyandb_stream_v2_query_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   3,
+			NumMessages:   4,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/proto/banyandb/stream/v2/query.proto b/api/proto/banyandb/stream/v2/query.proto
index e351a45..530de48 100644
--- a/api/proto/banyandb/stream/v2/query.proto
+++ b/api/proto/banyandb/stream/v2/query.proto
@@ -36,15 +36,13 @@ message Element {
   // 1) either the start time of a Span/Segment,
   // 2) or the timestamp of a log
   google.protobuf.Timestamp timestamp = 2;
-  // data_binary contains all un-indexed Tags and other key-value pairs
-  bytes data_binary = 3;
   // fields contains all indexed Field. Some typical names,
   // - stream_id
   // - duration
   // - service_name
   // - service_instance_id
   // - end_time_nanoseconds
-  repeated model.v2.TypedPair tags = 4;
+  repeated model.v2.TagFamily tag_families = 3;
 }
 
 // QueryResponse is the response for a query to the Query module.
@@ -68,8 +66,12 @@ message QueryRequest {
   uint32 limit = 4;
   // order_by is given to specify the sort for a field. So far, only fields in the type of Integer are supported
   model.v2.QueryOrder order_by = 5;
-  // tags are indexed. Some typical tags are listed below,
-  repeated model.v2.PairQuery tags = 6;
+  // tag_families are indexed.
+  message Criteria {
+    string tag_family_name = 1;
+    repeated model.v2.Condition conditions = 2;
+  }
+  repeated Criteria criteria = 6;
   // projection can be used to select the key names of the element in the response
   model.v2.Projection projection = 7;
 }
diff --git a/api/proto/banyandb/stream/v2/write.pb.go b/api/proto/banyandb/stream/v2/write.pb.go
index ecc7655..608436d 100644
--- a/api/proto/banyandb/stream/v2/write.pb.go
+++ b/api/proto/banyandb/stream/v2/write.pb.go
@@ -210,7 +210,7 @@ type ElementValue_TagFamily struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Tags []*v21.Tag `protobuf:"bytes,1,rep,name=tags,proto3" json:"tags,omitempty"`
+	Tags []*v21.TagValue `protobuf:"bytes,1,rep,name=tags,proto3" json:"tags,omitempty"`
 }
 
 func (x *ElementValue_TagFamily) Reset() {
@@ -245,7 +245,7 @@ func (*ElementValue_TagFamily) Descriptor() ([]byte, []int) {
 	return file_banyandb_stream_v2_write_proto_rawDescGZIP(), []int{0, 0}
 }
 
-func (x *ElementValue_TagFamily) GetTags() []*v21.Tag {
+func (x *ElementValue_TagFamily) GetTags() []*v21.TagValue {
 	if x != nil {
 		return x.Tags
 	}
@@ -262,41 +262,41 @@ var file_banyandb_stream_v2_write_proto_rawDesc = []byte{
 	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f,
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xef, 0x01, 0x0a, 0x0c, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
-	0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
-	0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6c, 0x65, 0x6d,
-	0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
-	0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
-	0x4d, 0x0a, 0x0c, 0x74, 0x61, 0x67, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x18,
-	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65,
-	0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c,
-	0x79, 0x52, 0x0b, 0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x1a, 0x37,
-	0x0a, 0x09, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x04, 0x74,
-	0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x61, 0x6e, 0x79,
-	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x61,
-	0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x0c, 0x57, 0x72, 0x69, 0x74,
-	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e,
-	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-	0x74, 0x61, 0x12, 0x3a, 0x0a, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73,
-	0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x0f,
-	0x0a, 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42,
-	0x6e, 0x0a, 0x28, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b,
-	0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x5a, 0x42, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73,
-	0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x76, 0x32, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
+	0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf4, 0x01, 0x0a, 0x0c, 0x45, 0x6c, 0x65, 0x6d, 0x65,
+	0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65,
+	0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6c, 0x65,
+	0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
+	0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+	0x12, 0x4d, 0x0a, 0x0c, 0x74, 0x61, 0x67, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73,
+	0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6c, 0x65, 0x6d,
+	0x65, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69,
+	0x6c, 0x79, 0x52, 0x0b, 0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x1a,
+	0x3c, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x2f, 0x0a, 0x04,
+	0x74, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e,
+	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x32, 0x2e, 0x54,
+	0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x22, 0x84, 0x01,
+	0x0a, 0x0c, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38,
+	0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
+	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x07, 0x65, 0x6c, 0x65, 0x6d,
+	0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x32, 0x2e, 0x45,
+	0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x65, 0x6c, 0x65,
+	0x6d, 0x65, 0x6e, 0x74, 0x22, 0x0f, 0x0a, 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x6e, 0x0a, 0x28, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61,
+	0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76,
+	0x32, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70,
+	0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d,
+	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -319,14 +319,14 @@ var file_banyandb_stream_v2_write_proto_goTypes = []interface{}{
 	(*ElementValue_TagFamily)(nil), // 3: banyandb.stream.v2.ElementValue.TagFamily
 	(*timestamppb.Timestamp)(nil),  // 4: google.protobuf.Timestamp
 	(*v2.Metadata)(nil),            // 5: banyandb.common.v2.Metadata
-	(*v21.Tag)(nil),                // 6: banyandb.model.v2.Tag
+	(*v21.TagValue)(nil),           // 6: banyandb.model.v2.TagValue
 }
 var file_banyandb_stream_v2_write_proto_depIdxs = []int32{
 	4, // 0: banyandb.stream.v2.ElementValue.timestamp:type_name -> google.protobuf.Timestamp
 	3, // 1: banyandb.stream.v2.ElementValue.tag_families:type_name -> banyandb.stream.v2.ElementValue.TagFamily
 	5, // 2: banyandb.stream.v2.WriteRequest.metadata:type_name -> banyandb.common.v2.Metadata
 	0, // 3: banyandb.stream.v2.WriteRequest.element:type_name -> banyandb.stream.v2.ElementValue
-	6, // 4: banyandb.stream.v2.ElementValue.TagFamily.tags:type_name -> banyandb.model.v2.Tag
+	6, // 4: banyandb.stream.v2.ElementValue.TagFamily.tags:type_name -> banyandb.model.v2.TagValue
 	5, // [5:5] is the sub-list for method output_type
 	5, // [5:5] is the sub-list for method input_type
 	5, // [5:5] is the sub-list for extension type_name
diff --git a/api/proto/banyandb/stream/v2/write.proto b/api/proto/banyandb/stream/v2/write.proto
index 39ef649..c845dbc 100644
--- a/api/proto/banyandb/stream/v2/write.proto
+++ b/api/proto/banyandb/stream/v2/write.proto
@@ -24,9 +24,7 @@ package banyandb.stream.v2;
 
 import "google/protobuf/timestamp.proto";
 import "banyandb/common/v2/common.proto";
-import "banyandb/model/v2/write.proto";
-
-
+import "banyandb/model/v2/common.proto";
 
 message ElementValue {
   // element_id could be span_id of a Span or segment_id of a Segment in the context of stream
@@ -36,7 +34,7 @@ message ElementValue {
   // 2) or the timestamp of a log
   google.protobuf.Timestamp timestamp = 2;
   message TagFamily {
-    repeated model.v2.Tag tags = 1;
+    repeated model.v2.TagValue tags = 1;
   }
   // the order of tag_families' items match the stream schema
   repeated TagFamily tag_families = 3;
diff --git a/banyand/index/tsdb/field_map.go b/banyand/index/tsdb/field_map.go
index 4d89713..6cc52f8 100644
--- a/banyand/index/tsdb/field_map.go
+++ b/banyand/index/tsdb/field_map.go
@@ -53,7 +53,7 @@ func (fm *fieldMap) get(key []byte) (*fieldValue, bool) {
 func (fm *fieldMap) put(fv *Field, id common.ChunkID) error {
 	pm, ok := fm.get(fv.Name)
 	if !ok {
-		return errors.Wrapf(ErrFieldAbsent, "filed Name:%s", fv.Name)
+		return errors.Wrapf(ErrFieldAbsent, "filed Term:%s", fv.Name)
 	}
 	return pm.value.put(fv.Value, id)
 }
diff --git a/banyand/kv/badger.go b/banyand/kv/badger.go
index 8a35c29..0dfbbe7 100644
--- a/banyand/kv/badger.go
+++ b/banyand/kv/badger.go
@@ -27,9 +27,13 @@ import (
 	"github.com/dgraph-io/badger/v3/y"
 	"go.uber.org/multierr"
 
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting/roaring"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
-	"github.com/apache/skywalking-banyandb/pkg/posting"
-	"github.com/apache/skywalking-banyandb/pkg/posting/roaring"
+	posting2 "github.com/apache/skywalking-banyandb/pkg/posting"
+	roaring2 "github.com/apache/skywalking-banyandb/pkg/posting/roaring"
 )
 
 var (
@@ -56,7 +60,7 @@ func (b *badgerTSS) Close() error {
 }
 
 type mergedIter struct {
-	delegated Iterator
+	delegated Iterator2
 	valid     bool
 	data      []byte
 }
@@ -116,26 +120,26 @@ type badgerDB struct {
 	db      *badger.DB
 }
 
-func (b *badgerDB) Handover(iterator Iterator) error {
+func (b *badgerDB) Handover(iterator Iterator2) error {
 	return b.db.HandoverIterator(&mergedIter{
 		delegated: iterator,
 	})
 }
 
-func (b *badgerDB) Seek(key []byte, limit int) (posting.List, error) {
+func (b *badgerDB) Seek(key []byte, limit int) (posting2.List, error) {
 	opts := badger.DefaultIteratorOptions
 	it := b.db.NewIterator(opts)
 	defer func() {
 		_ = it.Close()
 	}()
-	result := roaring.NewPostingList()
+	result := roaring2.NewPostingList()
 	var errMerged error
 	for it.Seek(y.KeyWithTs(key, math.MaxInt64)); it.Valid(); it.Next() {
 		k := y.ParseKey(it.Key())
 		if !bytes.Equal(key, k) {
 			break
 		}
-		list := roaring.NewPostingList()
+		list := roaring2.NewPostingList()
 		err := list.Unmarshall(it.Value().Value)
 		if err != nil {
 			errMerged = multierr.Append(errMerged, err)
@@ -153,6 +157,7 @@ func (b *badgerDB) Scan(key []byte, opt ScanOpts, f ScanFunc) error {
 	opts := badger.DefaultIteratorOptions
 	opts.PrefetchSize = opt.PrefetchSize
 	opts.PrefetchValues = opt.PrefetchValues
+	opts.Reverse = opt.Reverse
 	it := b.db.NewIterator(opts)
 	defer func() {
 		_ = it.Close()
@@ -172,6 +177,56 @@ func (b *badgerDB) Scan(key []byte, opt ScanOpts, f ScanFunc) error {
 	return nil
 }
 
+var _ Iterator = (*iterator)(nil)
+
+type iterator struct {
+	delegated y.Iterator
+}
+
+func (i *iterator) Next() {
+	i.delegated.Next()
+}
+
+func (i *iterator) Rewind() {
+	i.delegated.Rewind()
+}
+
+func (i *iterator) Seek(key []byte) {
+	i.delegated.Seek(key)
+}
+
+func (i *iterator) Key() []byte {
+	return i.delegated.Key()
+}
+
+func (i *iterator) Val() posting.List {
+	list := roaring.NewPostingList()
+	data := make([]byte, len(i.delegated.Value().Value))
+	copy(data, i.delegated.Value().Value)
+	_ = list.Unmarshall(data)
+	return list
+}
+
+func (i *iterator) Valid() bool {
+	return i.delegated.Valid()
+}
+
+func (i *iterator) Close() error {
+	return i.delegated.Close()
+}
+
+func (b *badgerDB) NewIterator(opt ScanOpts) Iterator {
+	opts := badger.DefaultIteratorOptions
+	opts.PrefetchSize = opt.PrefetchSize
+	opts.PrefetchValues = opt.PrefetchValues
+	opts.Reverse = opt.Reverse
+	opts.Prefix = opt.Prefix
+	it := b.db.NewIterator(opts)
+	return &iterator{
+		delegated: it,
+	}
+}
+
 func (b *badgerDB) Close() error {
 	if b.db != nil && !b.db.IsClosed() {
 		return b.db.Close()
@@ -183,6 +238,10 @@ func (b *badgerDB) Put(key, val []byte) error {
 	return b.db.Put(y.KeyWithTs(key, math.MaxInt64), val)
 }
 
+func (b *badgerDB) PutWithVersion(key, val []byte, version uint64) error {
+	return b.db.Put(y.KeyWithTs(key, version), val)
+}
+
 func (b *badgerDB) Get(key []byte) ([]byte, error) {
 	v, err := b.db.Get(y.KeyWithTs(key, math.MaxInt64))
 	if err == badger.ErrKeyNotFound {
@@ -194,6 +253,75 @@ func (b *badgerDB) Get(key []byte) ([]byte, error) {
 	return v.Value, nil
 }
 
+func (b *badgerDB) GetAll(key []byte, applyFn func([]byte) error) error {
+	iter := b.db.NewIterator(badger.DefaultIteratorOptions)
+	var count int
+	for iter.Seek(key); iter.Valid(); iter.Next() {
+		count++
+		err := applyFn(y.Copy(iter.Value().Value))
+		if err != nil {
+			return err
+		}
+	}
+	if count > 0 {
+		return nil
+	}
+	return ErrKeyNotFound
+}
+
+func (b *badgerDB) MatchField(fieldName []byte) (list posting.List) {
+	panic("implement me")
+}
+
+func (b *badgerDB) MatchTerms(field index.Field) (list posting.List) {
+	panic("implement me")
+}
+
+func (b *badgerDB) Range(fieldName []byte, opts index.RangeOpts) (list posting.List) {
+	panic("implement me")
+}
+
+var _ index.FieldIterator = (*fIterator)(nil)
+
+type fIterator struct {
+	init     bool
+	delegate Iterator
+}
+
+func (f *fIterator) Next() bool {
+	if f.init {
+		f.delegate.Next()
+	} else {
+		f.init = true
+	}
+	return f.delegate.Valid()
+}
+
+func (f *fIterator) Val() *index.PostingValue {
+	return &index.PostingValue{
+		Key:   f.delegate.Key(),
+		Value: f.delegate.Val(),
+	}
+}
+
+func (f *fIterator) Close() error {
+	return f.delegate.Close()
+}
+
+func (b *badgerDB) FieldIterator(fieldName []byte, order modelv2.QueryOrder_Sort) index.FieldIterator {
+	var reverse bool
+	if order == modelv2.QueryOrder_SORT_DESC {
+		reverse = true
+	}
+	iter := b.NewIterator(ScanOpts{
+		Prefix:  fieldName,
+		Reverse: reverse,
+	})
+	return &fIterator{
+		delegate: iter,
+	}
+}
+
 // badgerLog delegates the zap log to the badger logger
 type badgerLog struct {
 	*log.Logger
diff --git a/banyand/kv/kv.go b/banyand/kv/kv.go
index ccaa8be..5cea6ed 100644
--- a/banyand/kv/kv.go
+++ b/banyand/kv/kv.go
@@ -20,12 +20,15 @@ package kv
 import (
 	"fmt"
 	"io"
+	"math"
 
 	"github.com/dgraph-io/badger/v3"
 	"github.com/pkg/errors"
 
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
-	"github.com/apache/skywalking-banyandb/pkg/posting"
+	posting2 "github.com/apache/skywalking-banyandb/pkg/posting"
 )
 
 var (
@@ -39,6 +42,7 @@ var (
 type Writer interface {
 	// Put a value
 	Put(key, val []byte) error
+	PutWithVersion(key, val []byte, version uint64) error
 }
 
 type ScanFunc func(shardID int, key []byte, getVal func() ([]byte, error)) error
@@ -46,11 +50,14 @@ type ScanFunc func(shardID int, key []byte, getVal func() ([]byte, error)) error
 type ScanOpts struct {
 	PrefetchSize   int
 	PrefetchValues bool
+	Reverse        bool
+	Prefix         []byte
 }
 
 type Reader interface {
 	// Get a value by its key
 	Get(key []byte) ([]byte, error)
+	GetAll(key []byte, applyFn func([]byte) error) error
 	Scan(prefix []byte, opt ScanOpts, f ScanFunc) error
 }
 
@@ -59,6 +66,7 @@ type Store interface {
 	io.Closer
 	Writer
 	Reader
+	index.Searcher
 }
 
 type TimeSeriesWriter interface {
@@ -106,11 +114,21 @@ type Iterator interface {
 	Close() error
 }
 
+type Iterator2 interface {
+	Next()
+	Rewind()
+	Seek(key []byte)
+	Key() []byte
+	Val() posting2.List
+	Valid() bool
+	Close() error
+}
+
 type HandoverCallback func()
 
 type IndexStore interface {
-	Handover(iterator Iterator) error
-	Seek(key []byte, limit int) (posting.List, error)
+	Handover(iterator Iterator2) error
+	Seek(key []byte, limit int) (posting2.List, error)
 	Close() error
 }
 
@@ -180,7 +198,8 @@ func OpenStore(shardID int, path string, options ...StoreOptions) (Store, error)
 	for _, opt := range options {
 		opt(bdb)
 	}
-	bdb.dbOpts = bdb.dbOpts.WithMaxLevels(1)
+	bdb.dbOpts = bdb.dbOpts.WithMaxLevels(2)
+	bdb.dbOpts = bdb.dbOpts.WithNumVersionsToKeep(math.MaxUint32)
 
 	var err error
 	bdb.db, err = badger.Open(bdb.dbOpts)
@@ -211,9 +230,8 @@ func OpenIndexStore(shardID int, path string, options ...IndexOptions) (IndexSto
 	for _, opt := range options {
 		opt(bdb)
 	}
-	bdb.dbOpts = bdb.dbOpts.WithMaxLevels(1)
-	// Put all values into LSM
-	bdb.dbOpts = bdb.dbOpts.WithVLogPercentile(1.0)
+	bdb.dbOpts = bdb.dbOpts.WithMaxLevels(2)
+	bdb.dbOpts = bdb.dbOpts.WithNumVersionsToKeep(math.MaxUint32)
 
 	var err error
 	bdb.db, err = badger.Open(bdb.dbOpts)
diff --git a/banyand/storage/database_test.go b/banyand/storage/database_test.go
index 89c910e..971cdf7 100644
--- a/banyand/storage/database_test.go
+++ b/banyand/storage/database_test.go
@@ -168,7 +168,7 @@ func TestDB_FlushCallback(t *testing.T) {
 	}
 }
 
-var _ kv.Iterator = (*iter)(nil)
+var _ kv.Iterator2 = (*iter)(nil)
 
 type iter struct {
 	data map[int]posting.List
@@ -204,7 +204,7 @@ func (i *iter) Close() error {
 	return nil
 }
 
-func mockMemtable(data ...[]uint64) kv.Iterator {
+func mockMemtable(data ...[]uint64) kv.Iterator2 {
 	it := &iter{
 		data: make(map[int]posting.List),
 	}
diff --git a/banyand/stream/index.go b/banyand/stream/index.go
index 1d9a463..cfbf8e9 100644
--- a/banyand/stream/index.go
+++ b/banyand/stream/index.go
@@ -22,9 +22,11 @@ import (
 
 	"go.uber.org/multierr"
 
+	"github.com/apache/skywalking-banyandb/api/common"
 	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
 	streamv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/stream/v2"
 	"github.com/apache/skywalking-banyandb/banyand/tsdb"
+	"github.com/apache/skywalking-banyandb/pkg/index"
 	"github.com/apache/skywalking-banyandb/pkg/partition"
 )
 
@@ -60,7 +62,7 @@ func (s *stream) bootIndexGenerator() {
 }
 
 //TODO: should listen to pipeline in a distributed cluster
-func (s *stream) writeGlobalIndex(ruleIndex indexRule, ref tsdb.ItemID, value *streamv2.ElementValue) error {
+func (s *stream) writeGlobalIndex(ruleIndex indexRule, ref tsdb.GlobalItemID, value *streamv2.ElementValue) error {
 	val, err := getIndexValue(ruleIndex, value)
 	if err != nil {
 		return err
@@ -69,11 +71,11 @@ func (s *stream) writeGlobalIndex(ruleIndex indexRule, ref tsdb.ItemID, value *s
 	if err != nil {
 		return err
 	}
-	shard, err := s.db.Shard(indexShardID)
+	shard, err := s.db.Shard(common.ShardID(indexShardID))
 	if err != nil {
 		return err
 	}
-	builder := shard.Index().IndexWriterBuilder()
+	builder := shard.Index().WriterBuilder()
 	indexWriter, err := builder.
 		GlobalItemID(ref).
 		Time(value.GetTimestamp().AsTime()).
@@ -84,9 +86,15 @@ func (s *stream) writeGlobalIndex(ruleIndex indexRule, ref tsdb.ItemID, value *s
 	rule := ruleIndex.rule
 	switch rule.GetType() {
 	case databasev2.IndexRule_TYPE_INVERTED:
-		return indexWriter.WriteInvertedIndex(rule.Metadata.Name, val)
+		return indexWriter.WriteInvertedIndex(index.Field{
+			Term:  []byte(rule.Metadata.Name),
+			Value: val,
+		})
 	case databasev2.IndexRule_TYPE_TREE:
-		return indexWriter.WriteLSMIndex(rule.Metadata.Name, val)
+		return indexWriter.WriteLSMIndex(index.Field{
+			Term:  []byte(rule.Metadata.Name),
+			Value: val,
+		})
 	}
 	return err
 }
@@ -99,9 +107,15 @@ func writeLocalIndex(writer tsdb.Writer, ruleIndex indexRule, value *streamv2.El
 	rule := ruleIndex.rule
 	switch rule.GetType() {
 	case databasev2.IndexRule_TYPE_INVERTED:
-		return writer.WriteInvertedIndex(rule.Metadata.Name, val)
+		return writer.WriteInvertedIndex(index.Field{
+			Term:  []byte(rule.Metadata.Name),
+			Value: val,
+		})
 	case databasev2.IndexRule_TYPE_TREE:
-		return writer.WriteLSMIndex(rule.Metadata.Name, val)
+		return writer.WriteLSMIndex(index.Field{
+			Term:  []byte(rule.Metadata.Name),
+			Value: val,
+		})
 	}
 	return err
 }
diff --git a/banyand/stream/service.go b/banyand/stream/service.go
index d65978e..d7d1284 100644
--- a/banyand/stream/service.go
+++ b/banyand/stream/service.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	commonv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v2"
 	"github.com/apache/skywalking-banyandb/banyand/metadata"
 	"github.com/apache/skywalking-banyandb/banyand/metadata/schema"
 	"github.com/apache/skywalking-banyandb/banyand/queue"
@@ -29,12 +30,16 @@ import (
 	"github.com/apache/skywalking-banyandb/pkg/run"
 )
 
-var ErrEmptyRootPath = errors.New("root path is empty")
+var (
+	ErrEmptyRootPath  = errors.New("root path is empty")
+	ErrStreamNotExist = errors.New("stream doesn't exist")
+)
 
 type Service interface {
 	run.PreRunner
 	run.Config
 	run.Service
+	Query
 }
 
 var _ Service = (*service)(nil)
@@ -47,6 +52,15 @@ type service struct {
 	root          string
 }
 
+func (s *service) Stream(stream *commonv2.Metadata) (Stream, error) {
+	sID := formatStreamID(stream.GetName(), stream.GetGroup())
+	sm, ok := s.schemaMap[sID]
+	if !ok {
+		return nil, errors.WithStack(ErrStreamNotExist)
+	}
+	return sm, nil
+}
+
 func (s *service) FlagSet() *run.FlagSet {
 	flagS := run.NewFlagSet("storage")
 	flagS.StringVar(&s.root, "root-path", "/tmp", "the root path of database")
diff --git a/banyand/stream/stream.go b/banyand/stream/stream.go
index 338e1af..ffb4fc0 100644
--- a/banyand/stream/stream.go
+++ b/banyand/stream/stream.go
@@ -26,6 +26,8 @@ import (
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 )
 
+const strDelimiter = "\n"
+
 type tagIndex struct {
 	family int
 	tag    int
@@ -35,6 +37,7 @@ type indexRule struct {
 	rule       *databasev2.IndexRule
 	tagIndices []tagIndex
 }
+
 type stream struct {
 	name           string
 	group          string
@@ -102,8 +105,9 @@ func openStream(root string, spec streamSpec, l *logger.Logger) (*stream, error)
 	db, err := tsdb.OpenDatabase(
 		context.WithValue(context.Background(), logger.ContextKey, l),
 		tsdb.DatabaseOpts{
-			Location: root,
-			ShardNum: sm.schema.GetShardNum(),
+			Location:   root,
+			ShardNum:   sm.schema.GetShardNum(),
+			IndexRules: spec.indexRules,
 		})
 	if err != nil {
 		return nil, err
@@ -117,19 +121,19 @@ func formatStreamID(name, group string) string {
 	return name + ":" + group
 }
 
-func tagValueTypeConv(tag *modelv2.Tag) (tagType databasev2.TagType, isNull bool) {
-	switch tag.GetValueType().(type) {
-	case *modelv2.Tag_Int:
+func tagValueTypeConv(tagValue *modelv2.TagValue) (tagType databasev2.TagType, isNull bool) {
+	switch tagValue.GetValue().(type) {
+	case *modelv2.TagValue_Int:
 		return databasev2.TagType_TAG_TYPE_INT, false
-	case *modelv2.Tag_Str:
+	case *modelv2.TagValue_Str:
 		return databasev2.TagType_TAG_TYPE_STRING, false
-	case *modelv2.Tag_IntArray:
+	case *modelv2.TagValue_IntArray:
 		return databasev2.TagType_TAG_TYPE_INT_ARRAY, false
-	case *modelv2.Tag_StrArray:
+	case *modelv2.TagValue_StrArray:
 		return databasev2.TagType_TAG_TYPE_STRING_ARRAY, false
-	case *modelv2.Tag_BinaryData:
+	case *modelv2.TagValue_BinaryData:
 		return databasev2.TagType_TAG_TYPE_DATA_BINARY, false
-	case *modelv2.Tag_Null:
+	case *modelv2.TagValue_Null:
 		return databasev2.TagType_TAG_TYPE_UNSPECIFIED, true
 	}
 	return databasev2.TagType_TAG_TYPE_UNSPECIFIED, false
diff --git a/banyand/stream/stream_query.go b/banyand/stream/stream_query.go
new file mode 100644
index 0000000..ac7d038
--- /dev/null
+++ b/banyand/stream/stream_query.go
@@ -0,0 +1,117 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package stream
+
+import (
+	"bytes"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	commonv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v2"
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	streamv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/stream/v2"
+	"github.com/apache/skywalking-banyandb/banyand/tsdb"
+	"github.com/apache/skywalking-banyandb/pkg/partition"
+)
+
+var (
+	ErrTagNotExist       = errors.New("the tag doesn't exist")
+	ErrTagFamilyNotExist = errors.New("tag family doesn't exist")
+)
+
+type Query interface {
+	Stream(stream *commonv2.Metadata) (Stream, error)
+}
+
+type EqualCondition struct {
+	tag   string
+	value []byte
+}
+
+type Stream interface {
+	Shards(equalConditions []EqualCondition) ([]tsdb.Shard, error)
+}
+
+var _ Stream = (*stream)(nil)
+
+func (s *stream) Shards(equalConditions []EqualCondition) ([]tsdb.Shard, error) {
+	entityItemLen := len(s.entityIndex)
+	entityItems := make([][]byte, entityItemLen)
+	var entityCount int
+	for _, ec := range equalConditions {
+		fi, ti, tag := s.findTagByName(ec.tag)
+		if tag == nil {
+			return nil, ErrTagNotExist
+		}
+		for i, eIndex := range s.entityIndex {
+			if eIndex.family == fi && eIndex.tag == ti {
+				entityItems[i] = ec.value
+				entityCount++
+			}
+		}
+	}
+	if entityCount < entityItemLen {
+		return s.db.Shards(), nil
+	}
+	shardID, err := partition.ShardID(bytes.Join(entityItems, nil), s.schema.GetShardNum())
+	if err != nil {
+		return nil, err
+	}
+	shard, err := s.db.Shard(common.ShardID(shardID))
+	if err != nil {
+		return nil, err
+	}
+	return []tsdb.Shard{shard}, nil
+}
+
+func (s *stream) ParseTagFamily(family string, item tsdb.Item) (*modelv2.TagFamily, error) {
+	familyRawBytes, err := item.Val(family)
+	if err != nil {
+		return nil, err
+	}
+	tagFamily := &streamv2.ElementValue_TagFamily{}
+	err = proto.Unmarshal(familyRawBytes, tagFamily)
+	if err != nil {
+		return nil, err
+	}
+	tags := make([]*modelv2.Tag, len(tagFamily.GetTags()))
+	var tagSpec []*databasev2.TagSpec
+	for _, tf := range s.schema.GetTagFamilies() {
+		if tf.GetName() == family {
+			tagSpec = tf.GetTags()
+		}
+	}
+	if tagSpec == nil {
+		return nil, ErrTagFamilyNotExist
+	}
+	for i, tag := range tagFamily.GetTags() {
+		tags[i] = &modelv2.Tag{
+			Key: tagSpec[i].GetName(),
+			Value: &modelv2.TagValue{
+				Value: tag.GetValue(),
+			},
+		}
+	}
+	return &modelv2.TagFamily{
+		Name: family,
+		Tags: tags,
+	}, err
+}
diff --git a/banyand/stream/stream_write.go b/banyand/stream/stream_write.go
index 7678ab8..2bcd0bf 100644
--- a/banyand/stream/stream_write.go
+++ b/banyand/stream/stream_write.go
@@ -18,9 +18,13 @@
 package stream
 
 import (
+	"bytes"
+	"strings"
+
 	"github.com/pkg/errors"
 	"google.golang.org/protobuf/proto"
 
+	"github.com/apache/skywalking-banyandb/api/common"
 	"github.com/apache/skywalking-banyandb/api/data"
 	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
 	streamv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/stream/v2"
@@ -31,11 +35,12 @@ import (
 )
 
 var (
-	ErrMalformedElement          = errors.New("element is malformed")
-	ErrUnsupportedTagTypeAsEntry = errors.New("the tag type can not be as an entry in an entity")
+	ErrMalformedElement            = errors.New("element is malformed")
+	ErrUnsupportedTagTypeAsEntry   = errors.New("the tag type can not be as an entry in an entity")
+	ErrUnsupportedTagForIndexField = errors.New("the tag type(for example, null) can not be as the index field value")
 )
 
-func (s *stream) write(shardID uint, value *streamv2.ElementValue) error {
+func (s *stream) write(shardID common.ShardID, value *streamv2.ElementValue) error {
 	sm := s.schema
 	fLen := len(value.GetTagFamilies())
 	if fLen < 1 {
@@ -57,10 +62,7 @@ func (s *stream) write(shardID uint, value *streamv2.ElementValue) error {
 		return err
 	}
 	t := value.GetTimestamp().AsTime()
-	wp, err := series.Span(tsdb.TimeRange{
-		Start:    t,
-		Duration: 0,
-	})
+	wp, err := series.Span(tsdb.NewTimeRange(t, 0))
 	if err != nil {
 		if wp != nil {
 			_ = wp.Close()
@@ -125,7 +127,7 @@ func getIndexValue(ruleIndex indexRule, value *streamv2.ElementValue) (val []byt
 		if err != nil {
 			return nil, errors.WithMessagef(err, "index rule:%v", ruleIndex.rule.Metadata)
 		}
-		v, err := proto.Marshal(tag)
+		v, err := marshalIndexFieldValue(tag)
 		if err != nil {
 			return nil, err
 		}
@@ -134,22 +136,43 @@ func getIndexValue(ruleIndex indexRule, value *streamv2.ElementValue) (val []byt
 	return val, nil
 }
 
-func (s *stream) buildEntity(value *streamv2.ElementValue) (entity tsdb.Entity, err error) {
-	for _, index := range s.entityIndex {
+func marshalIndexFieldValue(tagValue *modelv2.TagValue) ([]byte, error) {
+	switch x := tagValue.GetValue().(type) {
+	case *modelv2.TagValue_Str:
+		return []byte(x.Str.GetValue()), nil
+	case *modelv2.TagValue_Int:
+		return convert.Int64ToBytes(x.Int.GetValue()), nil
+	case *modelv2.TagValue_StrArray:
+		return []byte(strings.Join(x.StrArray.GetValue(), strDelimiter)), nil
+	case *modelv2.TagValue_IntArray:
+		buf := bytes.NewBuffer(nil)
+		for _, i := range x.IntArray.GetValue() {
+			buf.Write(convert.Int64ToBytes(i))
+		}
+		return buf.Bytes(), nil
+	case *modelv2.TagValue_BinaryData:
+		return x.BinaryData, nil
+	}
+	return nil, ErrUnsupportedTagForIndexField
+}
+
+func (s *stream) buildEntity(value *streamv2.ElementValue) (tsdb.Entity, error) {
+	entity := make(tsdb.Entity, len(s.entityIndex))
+	for i, index := range s.entityIndex {
 		tag, err := getTagByOffset(value, index.family, index.tag)
 		if err != nil {
 			return nil, err
 		}
-		entry, err := tagConvEntry(tag)
-		if err != nil {
-			return nil, err
+		e, errMarshal := marshalIndexFieldValue(tag)
+		if errMarshal != nil {
+			return nil, errMarshal
 		}
-		entity = append(entity, entry)
+		entity[i] = e
 	}
 	return entity, nil
 }
 
-func getTagByOffset(value *streamv2.ElementValue, fIndex, tIndex int) (*modelv2.Tag, error) {
+func getTagByOffset(value *streamv2.ElementValue, fIndex, tIndex int) (*modelv2.TagValue, error) {
 	if fIndex >= len(value.TagFamilies) {
 		return nil, errors.Wrap(ErrMalformedElement, "tag family offset is invalid")
 	}
@@ -160,17 +183,6 @@ func getTagByOffset(value *streamv2.ElementValue, fIndex, tIndex int) (*modelv2.
 	return family.GetTags()[tIndex], nil
 }
 
-func tagConvEntry(tag *modelv2.Tag) (tsdb.Entry, error) {
-	switch tag.GetValueType().(type) {
-	case *modelv2.Tag_Str:
-		return tsdb.Entry(tag.GetStr().GetValue()), nil
-	case *modelv2.Tag_Int:
-		return convert.Int64ToBytes(tag.GetInt().GetValue()), nil
-	default:
-		return nil, ErrUnsupportedTagTypeAsEntry
-	}
-}
-
 type writeCallback struct {
 	l         *logger.Logger
 	schemaMap map[string]*stream
@@ -192,7 +204,7 @@ func (w *writeCallback) Rev(message bus.Message) (resp bus.Message) {
 	}
 	sm := writeEvent.WriteRequest.GetMetadata()
 	id := formatStreamID(sm.GetName(), sm.GetGroup())
-	err := w.schemaMap[id].write(writeEvent.ShardID, writeEvent.WriteRequest.GetElement())
+	err := w.schemaMap[id].write(common.ShardID(writeEvent.ShardID), writeEvent.WriteRequest.GetElement())
 	if err != nil {
 		w.l.Debug().Err(err)
 	}
diff --git a/banyand/stream/stream_write_test.go b/banyand/stream/stream_write_test.go
index f02ff0a..1853a7f 100644
--- a/banyand/stream/stream_write_test.go
+++ b/banyand/stream/stream_write_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"google.golang.org/protobuf/types/known/timestamppb"
 
+	"github.com/apache/skywalking-banyandb/api/common"
 	commonv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v2"
 	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
 	streamv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/stream/v2"
@@ -196,7 +197,7 @@ func Test_Stream_Write(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			err := s.write(tt.args.shardID, tt.args.ele)
+			err := s.write(common.ShardID(tt.args.shardID), tt.args.ele)
 			if tt.wantErr {
 				tester.Error(err)
 				return
@@ -237,7 +238,7 @@ func setup(t *assert.Assertions) (*stream, func()) {
 }
 
 func getEle(tags ...interface{}) *streamv2.ElementValue {
-	searchableTags := make([]*modelv2.Tag, 0)
+	searchableTags := make([]*modelv2.TagValue, 0)
 	for _, tag := range tags {
 		searchableTags = append(searchableTags, getTag(tag))
 	}
@@ -247,9 +248,9 @@ func getEle(tags ...interface{}) *streamv2.ElementValue {
 		Timestamp: timestamppb.Now(),
 		TagFamilies: []*streamv2.ElementValue_TagFamily{
 			{
-				Tags: []*modelv2.Tag{
+				Tags: []*modelv2.TagValue{
 					{
-						ValueType: &modelv2.Tag_BinaryData{
+						Value: &modelv2.TagValue_BinaryData{
 							BinaryData: bb,
 						},
 					},
@@ -263,24 +264,24 @@ func getEle(tags ...interface{}) *streamv2.ElementValue {
 	return e
 }
 
-func getTag(tag interface{}) *modelv2.Tag {
+func getTag(tag interface{}) *modelv2.TagValue {
 	if tag == nil {
-		return &modelv2.Tag{
-			ValueType: &modelv2.Tag_Null{},
+		return &modelv2.TagValue{
+			Value: &modelv2.TagValue_Null{},
 		}
 	}
 	switch t := tag.(type) {
 	case int:
-		return &modelv2.Tag{
-			ValueType: &modelv2.Tag_Int{
+		return &modelv2.TagValue{
+			Value: &modelv2.TagValue_Int{
 				Int: &modelv2.Int{
 					Value: int64(t),
 				},
 			},
 		}
 	case string:
-		return &modelv2.Tag{
-			ValueType: &modelv2.Tag_Str{
+		return &modelv2.TagValue{
+			Value: &modelv2.TagValue_Str{
 				Str: &modelv2.Str{
 					Value: t,
 				},
diff --git a/banyand/tsdb/block.go b/banyand/tsdb/block.go
index ad2483a..7d5168f 100644
--- a/banyand/tsdb/block.go
+++ b/banyand/tsdb/block.go
@@ -24,7 +24,12 @@ import (
 
 	"github.com/dgraph-io/ristretto/z"
 
+	"github.com/apache/skywalking-banyandb/api/common"
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
 	"github.com/apache/skywalking-banyandb/banyand/kv"
+	"github.com/apache/skywalking-banyandb/pkg/convert"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/inverted"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 )
 
@@ -33,13 +38,14 @@ type block struct {
 	l    *logger.Logger
 	ref  *z.Closer
 
-	store       kv.TimeSeriesStore
-	treeIndex   kv.Store
-	closableLst []io.Closer
-	endTime     time.Time
-	startTime   time.Time
-	segID       uint16
-	blockID     uint16
+	store         kv.TimeSeriesStore
+	primaryIndex  kv.Store
+	invertedIndex inverted.GlobalStore
+	closableLst   []io.Closer
+	endTime       time.Time
+	startTime     time.Time
+	segID         uint16
+	blockID       uint16
 
 	//revertedIndex kv.Store
 }
@@ -70,10 +76,27 @@ func newBlock(ctx context.Context, opts blockOpts) (b *block, err error) {
 		kv.TSSWithLogger(b.l)); err != nil {
 		return nil, err
 	}
-	if b.treeIndex, err = kv.OpenStore(0, b.path+"/t_index", kv.StoreWithLogger(b.l)); err != nil {
+	if b.primaryIndex, err = kv.OpenStore(0, b.path+"/p_index", kv.StoreWithLogger(b.l)); err != nil {
+		return nil, err
+	}
+	b.closableLst = append(b.closableLst, b.store, b.primaryIndex)
+	rules, ok := ctx.Value(indexRulesKey).([]*databasev2.IndexRule)
+	var specs []index.FieldSpec
+	for _, rule := range rules {
+		if rule.GetLocation() == databasev2.IndexRule_LOCATION_SERIES {
+			specs = append(specs, index.FieldSpec{
+				Name: rule.GetMetadata().GetName(),
+			})
+		}
+	}
+	if !ok || len(specs) == 0 {
+		return b, nil
+	}
+	b.invertedIndex = inverted.NewStore("inverted")
+	err = b.invertedIndex.Initialize(specs)
+	if err != nil {
 		return nil, err
 	}
-	b.closableLst = append(b.closableLst, b.store, b.treeIndex)
 	return b, nil
 }
 
@@ -104,9 +127,15 @@ type blockDelegate interface {
 	io.Closer
 	contains(ts time.Time) bool
 	write(key []byte, val []byte, ts time.Time) error
-	writeLSMIndex(key []byte, val []byte) error
-	writeInvertedIndex(key []byte, val []byte) error
+	writePrimaryIndex(field index.Field, id common.ItemID) error
+	writeLSMIndex(field index.Field, id common.ItemID) error
+	writeInvertedIndex(field index.Field, id common.ItemID) error
+	dataReader() kv.TimeSeriesReader
+	lsmIndexReader() index.Searcher
+	invertedIndexReader() index.Searcher
+	primaryIndexReader() index.Searcher
 	identity() (segID uint16, blockID uint16)
+	startTime() time.Time
 }
 
 var _ blockDelegate = (*bDelegate)(nil)
@@ -115,6 +144,26 @@ type bDelegate struct {
 	delegate *block
 }
 
+func (d *bDelegate) dataReader() kv.TimeSeriesReader {
+	return d.delegate.store
+}
+
+func (d *bDelegate) lsmIndexReader() index.Searcher {
+	return d.delegate.invertedIndex.Searcher()
+}
+
+func (d *bDelegate) invertedIndexReader() index.Searcher {
+	return d.delegate.invertedIndex.Searcher()
+}
+
+func (d *bDelegate) primaryIndexReader() index.Searcher {
+	return d.delegate.primaryIndex
+}
+
+func (d *bDelegate) startTime() time.Time {
+	return d.delegate.startTime
+}
+
 func (d *bDelegate) identity() (segID uint16, blockID uint16) {
 	return d.delegate.segID, d.delegate.blockID
 }
@@ -123,12 +172,22 @@ func (d *bDelegate) write(key []byte, val []byte, ts time.Time) error {
 	return d.delegate.store.Put(key, val, uint64(ts.UnixNano()))
 }
 
-func (d *bDelegate) writeLSMIndex(key []byte, val []byte) error {
-	return d.delegate.treeIndex.Put(key, val)
+func (d *bDelegate) writePrimaryIndex(field index.Field, id common.ItemID) error {
+	return d.delegate.primaryIndex.Put(field.Marshal(), convert.Uint64ToBytes(uint64(id)))
 }
 
-func (d *bDelegate) writeInvertedIndex(key []byte, val []byte) error {
-	return d.delegate.treeIndex.Put(key, val)
+func (d *bDelegate) writeLSMIndex(field index.Field, id common.ItemID) error {
+	if d.delegate.invertedIndex == nil {
+		return nil
+	}
+	return d.delegate.invertedIndex.Insert(field, id)
+}
+
+func (d *bDelegate) writeInvertedIndex(field index.Field, id common.ItemID) error {
+	if d.delegate.invertedIndex == nil {
+		return nil
+	}
+	return d.delegate.invertedIndex.Insert(field, id)
 }
 
 func (d *bDelegate) contains(ts time.Time) bool {
diff --git a/banyand/tsdb/indexdb.go b/banyand/tsdb/indexdb.go
index 0b3fb80..c81c727 100644
--- a/banyand/tsdb/indexdb.go
+++ b/banyand/tsdb/indexdb.go
@@ -18,30 +18,34 @@
 package tsdb
 
 import (
-	"bytes"
 	"context"
 	"time"
 
 	"github.com/pkg/errors"
 
 	"github.com/apache/skywalking-banyandb/api/common"
+	"github.com/apache/skywalking-banyandb/pkg/index"
 )
 
 type IndexDatabase interface {
-	IndexWriterBuilder() IndexWriterBuilder
+	WriterBuilder() IndexWriterBuilder
+	Seek(field index.Field) ([]GlobalItemID, error)
 }
 
 type IndexWriter interface {
-	WriteLSMIndex(name string, val []byte) error
-	WriteInvertedIndex(name string, val []byte) error
+	WriteLSMIndex(field index.Field) error
+	WriteInvertedIndex(field index.Field) error
 }
 
 type IndexWriterBuilder interface {
 	Time(ts time.Time) IndexWriterBuilder
-	GlobalItemID(itemID ItemID) IndexWriterBuilder
+	GlobalItemID(itemID GlobalItemID) IndexWriterBuilder
 	Build() (IndexWriter, error)
 }
 
+type IndexSeekBuilder interface {
+}
+
 var _ IndexDatabase = (*indexDB)(nil)
 
 type indexDB struct {
@@ -49,7 +53,21 @@ type indexDB struct {
 	lst     []*segment
 }
 
-func (i *indexDB) IndexWriterBuilder() IndexWriterBuilder {
+func (i *indexDB) Seek(term index.Field) ([]GlobalItemID, error) {
+	var result []GlobalItemID
+	err := i.lst[0].globalIndex.GetAll(term.Marshal(), func(rawBytes []byte) error {
+		id := &GlobalItemID{}
+		err := id.UnMarshal(rawBytes)
+		if err != nil {
+			return err
+		}
+		result = append(result, *id)
+		return nil
+	})
+	return result, err
+}
+
+func (i *indexDB) WriterBuilder() IndexWriterBuilder {
 	return newIndexWriterBuilder(i.lst)
 }
 
@@ -66,7 +84,7 @@ type indexWriterBuilder struct {
 	segments     []*segment
 	ts           time.Time
 	seg          *segment
-	globalItemID *ItemID
+	globalItemID *GlobalItemID
 }
 
 func (i *indexWriterBuilder) Time(ts time.Time) IndexWriterBuilder {
@@ -80,7 +98,7 @@ func (i *indexWriterBuilder) Time(ts time.Time) IndexWriterBuilder {
 	return i
 }
 
-func (i *indexWriterBuilder) GlobalItemID(itemID ItemID) IndexWriterBuilder {
+func (i *indexWriterBuilder) GlobalItemID(itemID GlobalItemID) IndexWriterBuilder {
 	i.globalItemID = &itemID
 	return i
 }
@@ -110,13 +128,13 @@ var _ IndexWriter = (*indexWriter)(nil)
 type indexWriter struct {
 	seg    *segment
 	ts     time.Time
-	itemID *ItemID
+	itemID *GlobalItemID
 }
 
-func (i *indexWriter) WriteLSMIndex(name string, val []byte) error {
-	return i.seg.globalIndex.Put(bytes.Join([][]byte{[]byte(name), val}, nil), i.itemID.Marshal())
+func (i *indexWriter) WriteLSMIndex(field index.Field) error {
+	return i.seg.globalIndex.PutWithVersion(field.Marshal(), i.itemID.Marshal(), uint64(i.ts.UnixNano()))
 }
 
-func (i *indexWriter) WriteInvertedIndex(name string, val []byte) error {
-	return i.seg.globalIndex.Put(bytes.Join([][]byte{[]byte(name), val}, nil), i.itemID.Marshal())
+func (i *indexWriter) WriteInvertedIndex(field index.Field) error {
+	return i.seg.globalIndex.PutWithVersion(field.Marshal(), i.itemID.Marshal(), uint64(i.ts.UnixNano()))
 }
diff --git a/banyand/tsdb/series.go b/banyand/tsdb/series.go
index bdbb7d4..efdf8ba 100644
--- a/banyand/tsdb/series.go
+++ b/banyand/tsdb/series.go
@@ -26,7 +26,6 @@ import (
 	"go.uber.org/multierr"
 
 	"github.com/apache/skywalking-banyandb/api/common"
-	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
 	"github.com/apache/skywalking-banyandb/pkg/convert"
 )
 
@@ -35,42 +34,26 @@ var (
 	ErrItemIDMalformed = errors.New("serialized item id is malformed")
 )
 
-type Iterator interface {
-	Next() bool
-	Val() Item
-	Close() error
-}
-
-type Item interface {
-	Val(family string) []byte
-	SortingVal() []byte
-}
-
-type ConditionValue struct {
-	Values [][]byte
-	Op     modelv2.PairQuery_BinaryOp
-}
-
-type Condition map[string][]ConditionValue
-
-type ItemID struct {
-	shardID common.ShardID
-	segID   uint16
-	blockID uint16
-	id      []byte
+type GlobalItemID struct {
+	shardID  common.ShardID
+	segID    uint16
+	blockID  uint16
+	seriesID common.SeriesID
+	id       common.ItemID
 }
 
-func (i *ItemID) Marshal() []byte {
+func (i *GlobalItemID) Marshal() []byte {
 	return bytes.Join([][]byte{
 		convert.Uint32ToBytes(uint32(i.shardID)),
 		convert.Uint16ToBytes(i.segID),
 		convert.Uint16ToBytes(i.blockID),
-		i.id,
+		convert.Uint64ToBytes(uint64(i.seriesID)),
+		convert.Uint64ToBytes(uint64(i.id)),
 	}, nil)
 }
 
-func (i *ItemID) UnMarshal(data []byte) error {
-	if len(data) <= 32+16+16 {
+func (i *GlobalItemID) UnMarshal(data []byte) error {
+	if len(data) <= 32+16+16+64+64 {
 		return ErrItemIDMalformed
 	}
 	var offset int
@@ -80,52 +63,46 @@ func (i *ItemID) UnMarshal(data []byte) error {
 	offset += 2
 	i.blockID = convert.BytesToUint16(data[offset : offset+2])
 	offset += 2
-	i.id = data[offset:]
+	i.seriesID = common.SeriesID(convert.BytesToUint64(data[offset : offset+8]))
+	offset += 8
+	i.id = common.ItemID(convert.BytesToUint64(data[offset:]))
 	return nil
 }
 
 type TimeRange struct {
 	Start    time.Time
 	Duration time.Duration
+	End      time.Time
+}
+
+func (t TimeRange) contains(unixNano uint64) bool {
+	tp := time.Unix(0, int64(unixNano))
+	if tp.Equal(t.End) || tp.After(t.End) {
+		return false
+	}
+	return tp.Equal(t.Start) || tp.After(t.Start)
+}
+
+func NewTimeRange(Start time.Time, Duration time.Duration) TimeRange {
+	return TimeRange{
+		Start:    Start,
+		Duration: Duration,
+		End:      Start.Add(Duration),
+	}
 }
 
 type Series interface {
 	ID() common.SeriesID
 	Span(timeRange TimeRange) (SeriesSpan, error)
-	Get(id ItemID) (Item, error)
+	Get(id GlobalItemID) (Item, error)
 }
 
 type SeriesSpan interface {
 	io.Closer
 	WriterBuilder() WriterBuilder
-	Iterator() Iterator
 	SeekerBuilder() SeekerBuilder
 }
 
-type WriterBuilder interface {
-	Family(name string, val []byte) WriterBuilder
-	Time(ts time.Time) WriterBuilder
-	Val(val []byte) WriterBuilder
-	Build() (Writer, error)
-}
-
-type Writer interface {
-	IndexWriter
-	Write() (ItemID, error)
-	ItemID() ItemID
-}
-
-type SeekerBuilder interface {
-	Filter(condition Condition) SeekerBuilder
-	OrderByIndex(name string, order modelv2.QueryOrder_Sort) SeekerBuilder
-	OrderByTime(order modelv2.QueryOrder_Sort) SeekerBuilder
-	Build() Seeker
-}
-
-type Seeker interface {
-	Seek() Iterator
-}
-
 var _ Series = (*series)(nil)
 
 type series struct {
@@ -134,6 +111,10 @@ type series struct {
 	shardID common.ShardID
 }
 
+func (s *series) Get(id GlobalItemID) (Item, error) {
+	panic("implement me")
+}
+
 func newSeries(id common.SeriesID, blockDB blockDatabase) *series {
 	return &series{
 		id:      id,
@@ -151,19 +132,16 @@ func (s *series) Span(timeRange TimeRange) (SeriesSpan, error) {
 	if len(blocks) < 1 {
 		return nil, ErrEmptySeriesSpan
 	}
-	return newSeriesSpan(blocks, s.id, s.shardID), nil
-}
-
-func (s *series) Get(id ItemID) (Item, error) {
-	panic("not implemented")
+	return newSeriesSpan(timeRange, blocks, s.id, s.shardID), nil
 }
 
 var _ SeriesSpan = (*seriesSpan)(nil)
 
 type seriesSpan struct {
-	blocks   []blockDelegate
-	seriesID common.SeriesID
-	shardID  common.ShardID
+	blocks    []blockDelegate
+	seriesID  common.SeriesID
+	shardID   common.ShardID
+	timeRange TimeRange
 }
 
 func (s *seriesSpan) Close() (err error) {
@@ -177,123 +155,16 @@ func (s *seriesSpan) WriterBuilder() WriterBuilder {
 	return newWriterBuilder(s)
 }
 
-func (s *seriesSpan) Iterator() Iterator {
-	panic("implement me")
-}
-
 func (s *seriesSpan) SeekerBuilder() SeekerBuilder {
-	panic("implement me")
+	return newSeekerBuilder(s)
 }
 
-func newSeriesSpan(blocks []blockDelegate, id common.SeriesID, shardID common.ShardID) *seriesSpan {
+func newSeriesSpan(timeRange TimeRange, blocks []blockDelegate,
+	id common.SeriesID, shardID common.ShardID) *seriesSpan {
 	return &seriesSpan{
-		blocks:   blocks,
-		seriesID: id,
-		shardID:  shardID,
-	}
-}
-
-var _ WriterBuilder = (*writerBuilder)(nil)
-
-type writerBuilder struct {
-	series *seriesSpan
-	block  blockDelegate
-	values []struct {
-		family []byte
-		val    []byte
-	}
-	ts            time.Time
-	seriesIDBytes []byte
-}
-
-func (w *writerBuilder) Family(name string, val []byte) WriterBuilder {
-	w.values = append(w.values, struct {
-		family []byte
-		val    []byte
-	}{family: bytes.Join([][]byte{w.seriesIDBytes, hash([]byte(name))}, nil), val: val})
-	return w
-}
-
-func (w *writerBuilder) Time(ts time.Time) WriterBuilder {
-	w.ts = ts
-	for _, b := range w.series.blocks {
-		if b.contains(ts) {
-			w.block = b
-			break
-		}
-	}
-	return w
-}
-
-func (w *writerBuilder) Val(val []byte) WriterBuilder {
-	w.values = append(w.values, struct {
-		family []byte
-		val    []byte
-	}{val: val})
-	return w
-}
-
-var ErrNoTime = errors.New("no time specified")
-var ErrNoVal = errors.New("no value specified")
-
-func (w *writerBuilder) Build() (Writer, error) {
-	if w.block == nil {
-		return nil, errors.WithStack(ErrNoTime)
-	}
-	if len(w.values) < 1 {
-		return nil, errors.WithStack(ErrNoVal)
-	}
-	segID, blockID := w.block.identity()
-	return &writer{
-		block: w.block,
-		ts:    w.ts,
-		itemID: &ItemID{
-			shardID: w.series.shardID,
-			segID:   segID,
-			blockID: blockID,
-			id:      bytes.Join([][]byte{w.seriesIDBytes, convert.Int64ToBytes(w.ts.UnixNano())}, nil),
-		},
-		columns: w.values,
-	}, nil
-}
-
-func newWriterBuilder(seriesSpan *seriesSpan) WriterBuilder {
-	return &writerBuilder{
-		series:        seriesSpan,
-		seriesIDBytes: convert.Uint64ToBytes(uint64(seriesSpan.seriesID)),
-	}
-}
-
-var _ Writer = (*writer)(nil)
-
-type writer struct {
-	block   blockDelegate
-	ts      time.Time
-	columns []struct {
-		family []byte
-		val    []byte
-	}
-	itemID *ItemID
-}
-
-func (w *writer) ItemID() ItemID {
-	return *w.itemID
-}
-
-func (w *writer) WriteLSMIndex(name string, val []byte) error {
-	return w.block.writeLSMIndex(bytes.Join([][]byte{[]byte(name), val}, nil), w.itemID.id)
-}
-
-func (w *writer) WriteInvertedIndex(name string, val []byte) error {
-	return w.block.writeInvertedIndex(bytes.Join([][]byte{[]byte(name), val}, nil), w.itemID.id)
-}
-
-func (w *writer) Write() (id ItemID, err error) {
-	for _, c := range w.columns {
-		err = w.block.write(c.family, c.val, w.ts)
-		if err != nil {
-			return id, err
-		}
+		blocks:    blocks,
+		seriesID:  id,
+		shardID:   shardID,
+		timeRange: timeRange,
 	}
-	return id, nil
 }
diff --git a/banyand/tsdb/series_seek.go b/banyand/tsdb/series_seek.go
new file mode 100644
index 0000000..478efa0
--- /dev/null
+++ b/banyand/tsdb/series_seek.go
@@ -0,0 +1,128 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tsdb
+
+import (
+	"github.com/apache/skywalking-banyandb/api/common"
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/banyand/kv"
+)
+
+type Iterator interface {
+	Next() bool
+	Val() Item
+	Close() error
+}
+
+type Item interface {
+	Val(family string) ([]byte, error)
+	ID() common.ItemID
+	SortedField() []byte
+	Time() uint64
+}
+
+type SeekerBuilder interface {
+	Filter(indexRule *databasev2.IndexRule, condition Condition) SeekerBuilder
+	OrderByIndex(indexRule *databasev2.IndexRule, order modelv2.QueryOrder_Sort) SeekerBuilder
+	OrderByTime(order modelv2.QueryOrder_Sort) SeekerBuilder
+	Build() (Seeker, error)
+}
+
+type Seeker interface {
+	Seek() ([]Iterator, error)
+}
+
+var _ SeekerBuilder = (*seekerBuilder)(nil)
+
+type seekerBuilder struct {
+	seriesSpan *seriesSpan
+
+	conditions []struct {
+		indexRuleType databasev2.IndexRule_Type
+		indexRule     string
+		condition     Condition
+	}
+	order               modelv2.QueryOrder_Sort
+	indexRuleForSorting *databasev2.IndexRule
+}
+
+func (s *seekerBuilder) Build() (Seeker, error) {
+	indexFilter, err := s.buildIndexFilter()
+	if err != nil {
+		return nil, err
+	}
+	filters := []filterFn{
+		indexFilter,
+		func(item Item) bool {
+			return s.seriesSpan.timeRange.contains(item.Time())
+		},
+	}
+	return newSeeker(s.buildSeries(filters)), nil
+}
+
+func newSeekerBuilder(s *seriesSpan) SeekerBuilder {
+	return &seekerBuilder{
+		seriesSpan: s,
+	}
+}
+
+var _ Seeker = (*seeker)(nil)
+
+type seeker struct {
+	series []Iterator
+}
+
+func (s *seeker) Seek() ([]Iterator, error) {
+	return s.series, nil
+}
+
+func newSeeker(series []Iterator) Seeker {
+	return &seeker{
+		series: series,
+	}
+}
+
+var _ Item = (*item)(nil)
+
+type item struct {
+	itemID      common.ItemID
+	data        kv.TimeSeriesReader
+	seriesID    common.SeriesID
+	sortedField []byte
+}
+
+func (i *item) Time() uint64 {
+	return uint64(i.itemID)
+}
+
+func (i *item) SortedField() []byte {
+	return i.sortedField
+}
+
+func (i *item) Val(family string) ([]byte, error) {
+	d := dataBucket{
+		seriesID: i.seriesID,
+		family:   []byte(family),
+	}
+	return i.data.Get(d.marshal(), uint64(i.itemID))
+}
+
+func (i *item) ID() common.ItemID {
+	return i.itemID
+}
diff --git a/banyand/tsdb/series_seek_filter.go b/banyand/tsdb/series_seek_filter.go
new file mode 100644
index 0000000..65147ea
--- /dev/null
+++ b/banyand/tsdb/series_seek_filter.go
@@ -0,0 +1,104 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tsdb
+
+import (
+	"github.com/pkg/errors"
+
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting/roaring"
+)
+
+var ErrUnsupportedIndexRule = errors.New("the index rule is not supported")
+
+type Condition map[string][]index.ConditionValue
+
+func (s *seekerBuilder) Filter(indexRule *databasev2.IndexRule, condition Condition) SeekerBuilder {
+	s.conditions = append(s.conditions, struct {
+		indexRuleType databasev2.IndexRule_Type
+		indexRule     string
+		condition     Condition
+	}{
+		indexRuleType: indexRule.GetType(),
+		indexRule:     indexRule.GetMetadata().GetName(),
+		condition:     condition,
+	})
+	return s
+}
+
+func (s *seekerBuilder) buildIndexFilter() (filterFn, error) {
+	var treeIndexCondition, invertedIndexCondition []index.Condition
+	for _, condition := range s.conditions {
+		if len(condition.condition) > 1 {
+			//TODO:// should support composite index rule
+			return nil, ErrUnsupportedIndexRule
+		}
+		var cond index.Condition
+		term := index.Term{
+			SeriesID:  s.seriesSpan.seriesID,
+			IndexRule: condition.indexRule,
+		}
+		for _, c := range condition.condition {
+			cond[term] = c
+			break
+		}
+		switch condition.indexRuleType {
+		case databasev2.IndexRule_TYPE_TREE:
+			treeIndexCondition = append(treeIndexCondition, cond)
+		case databasev2.IndexRule_TYPE_INVERTED:
+			invertedIndexCondition = append(invertedIndexCondition, cond)
+		}
+	}
+	allItemIDs := roaring.NewPostingList()
+	addIDs := func(allList posting.List, searcher index.Searcher, cond index.Condition) error {
+		tree, err := index.BuildTree(searcher, cond)
+		if err != nil {
+			return err
+		}
+		list, err := tree.Execute()
+		if err != nil {
+			return err
+		}
+		err = allList.Union(list)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+	for _, b := range s.seriesSpan.blocks {
+		for _, tCond := range treeIndexCondition {
+			err := addIDs(allItemIDs, b.lsmIndexReader(), tCond)
+			if err != nil {
+				return nil, err
+			}
+		}
+		for _, iCond := range invertedIndexCondition {
+			err := addIDs(allItemIDs, b.invertedIndexReader(), iCond)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return func(item Item) bool {
+		return allItemIDs.Contains(item.ID())
+	}, nil
+}
+
+type filterFn func(item Item) bool
diff --git a/banyand/tsdb/series_seek_sort.go b/banyand/tsdb/series_seek_sort.go
new file mode 100644
index 0000000..1646bce
--- /dev/null
+++ b/banyand/tsdb/series_seek_sort.go
@@ -0,0 +1,200 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tsdb
+
+import (
+	"sort"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/banyand/kv"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+)
+
+func (s *seekerBuilder) OrderByIndex(indexRule *databasev2.IndexRule, order modelv2.QueryOrder_Sort) SeekerBuilder {
+	s.indexRuleForSorting = indexRule
+	s.order = order
+	return s
+}
+
+func (s *seekerBuilder) OrderByTime(order modelv2.QueryOrder_Sort) SeekerBuilder {
+	s.order = order
+	s.indexRuleForSorting = nil
+	return s
+}
+
+func (s *seekerBuilder) buildSeries(filters []filterFn) []Iterator {
+	if s.indexRuleForSorting == nil {
+		return s.buildSeriesByIndex(filters)
+	}
+	return s.buildSeriesByTime(filters)
+}
+
+func (s *seekerBuilder) buildSeriesByIndex(filters []filterFn) (series []Iterator) {
+	for _, b := range s.seriesSpan.blocks {
+		switch s.indexRuleForSorting.GetType() {
+		case databasev2.IndexRule_TYPE_TREE:
+			series = append(series, newSearcherIterator(
+				b.lsmIndexReader().
+					FieldIterator([]byte(s.indexRuleForSorting.GetMetadata().GetName()), s.order),
+				b.dataReader(),
+				s.seriesSpan.seriesID,
+				filters,
+			))
+		case databasev2.IndexRule_TYPE_INVERTED:
+			series = append(series, newSearcherIterator(b.invertedIndexReader().
+				FieldIterator([]byte(s.indexRuleForSorting.GetMetadata().GetName()), s.order),
+				b.dataReader(),
+				s.seriesSpan.seriesID,
+				filters,
+			))
+		}
+	}
+	return
+}
+
+func (s *seekerBuilder) buildSeriesByTime(filters []filterFn) []Iterator {
+	bb := s.seriesSpan.blocks
+	switch s.order {
+	case modelv2.QueryOrder_SORT_ASC:
+		sort.SliceStable(bb, func(i, j int) bool {
+			return bb[i].startTime().Before(bb[j].startTime())
+		})
+
+	case modelv2.QueryOrder_SORT_DESC:
+		sort.SliceStable(bb, func(i, j int) bool {
+			return bb[i].startTime().After(bb[j].startTime())
+		})
+	}
+	delegated := make([]Iterator, len(bb))
+	for _, b := range bb {
+		delegated = append(delegated, newSearcherIterator(b.
+			primaryIndexReader().
+			FieldIterator(
+				s.seriesSpan.seriesID.Marshal(),
+				s.order,
+			), b.dataReader(), s.seriesSpan.seriesID, filters))
+	}
+	return []Iterator{newMergedIterator(delegated)}
+}
+
+var _ Iterator = (*searcherIterator)(nil)
+
+type searcherIterator struct {
+	fieldIterator index.FieldIterator
+	curKey        []byte
+	cur           posting.Iterator
+	data          kv.TimeSeriesReader
+	seriesID      common.SeriesID
+	filters       []filterFn
+}
+
+func (s *searcherIterator) Next() bool {
+	if s.cur == nil {
+		if s.fieldIterator.Next() {
+			v := s.fieldIterator.Val()
+			s.cur = v.Value.Iterator()
+			s.curKey = v.Key
+		} else {
+			_ = s.Close()
+			return false
+		}
+	}
+	if s.cur.Next() {
+		for _, filter := range s.filters {
+			if !filter(s.Val()) {
+				return s.Next()
+			}
+		}
+		return true
+	}
+	_ = s.cur.Close()
+	s.cur = nil
+	return s.Next()
+}
+
+func (s *searcherIterator) Val() Item {
+	return &item{
+		sortedField: s.curKey,
+		itemID:      s.cur.Current(),
+		data:        s.data,
+		seriesID:    s.seriesID,
+	}
+}
+
+func (s *searcherIterator) Close() error {
+	return s.fieldIterator.Close()
+}
+
+func newSearcherIterator(fieldIterator index.FieldIterator, data kv.TimeSeriesReader, seriesID common.SeriesID, filters []filterFn) Iterator {
+	return &searcherIterator{
+		fieldIterator: fieldIterator,
+		data:          data,
+		seriesID:      seriesID,
+		filters:       filters,
+	}
+}
+
+var _ Iterator = (*mergedIterator)(nil)
+
+type mergedIterator struct {
+	curr      Iterator
+	index     int
+	delegated []Iterator
+	closed    bool
+}
+
+func (m *mergedIterator) Next() bool {
+	if m.closed {
+		return false
+	}
+	if m.curr == nil {
+		m.index++
+		if m.index >= len(m.delegated) {
+			_ = m.Close()
+			return false
+		} else {
+			m.curr = m.delegated[m.index]
+		}
+	}
+	hasNext := m.curr.Next()
+	if !hasNext {
+		_ = m.curr.Close()
+		m.curr = nil
+		return m.Next()
+	}
+	return true
+}
+
+func (m *mergedIterator) Val() Item {
+	return m.curr.Val()
+}
+
+func (m *mergedIterator) Close() error {
+	m.closed = true
+	return nil
+}
+
+func newMergedIterator(delegated []Iterator) Iterator {
+	return &mergedIterator{
+		index:     -1,
+		delegated: delegated,
+	}
+}
diff --git a/banyand/tsdb/series_write.go b/banyand/tsdb/series_write.go
new file mode 100644
index 0000000..0f69332
--- /dev/null
+++ b/banyand/tsdb/series_write.go
@@ -0,0 +1,177 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tsdb
+
+import (
+	"bytes"
+	"time"
+
+	"github.com/pkg/errors"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	"github.com/apache/skywalking-banyandb/pkg/convert"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+)
+
+type WriterBuilder interface {
+	Family(name string, val []byte) WriterBuilder
+	Time(ts time.Time) WriterBuilder
+	Val(val []byte) WriterBuilder
+	Build() (Writer, error)
+}
+
+type Writer interface {
+	IndexWriter
+	Write() (GlobalItemID, error)
+	ItemID() GlobalItemID
+}
+
+var _ WriterBuilder = (*writerBuilder)(nil)
+
+type writerBuilder struct {
+	series *seriesSpan
+	block  blockDelegate
+	values []struct {
+		family []byte
+		val    []byte
+	}
+	ts            time.Time
+	seriesIDBytes []byte
+}
+
+func (w *writerBuilder) Family(name string, val []byte) WriterBuilder {
+	w.values = append(w.values, struct {
+		family []byte
+		val    []byte
+	}{family: []byte(name), val: val})
+	return w
+}
+
+func (w *writerBuilder) Time(ts time.Time) WriterBuilder {
+	w.ts = ts
+	for _, b := range w.series.blocks {
+		if b.contains(ts) {
+			w.block = b
+			break
+		}
+	}
+	return w
+}
+
+func (w *writerBuilder) Val(val []byte) WriterBuilder {
+	w.values = append(w.values, struct {
+		family []byte
+		val    []byte
+	}{val: val})
+	return w
+}
+
+var ErrNoTime = errors.New("no time specified")
+var ErrNoVal = errors.New("no value specified")
+
+func (w *writerBuilder) Build() (Writer, error) {
+	if w.block == nil {
+		return nil, errors.WithStack(ErrNoTime)
+	}
+	if len(w.values) < 1 {
+		return nil, errors.WithStack(ErrNoVal)
+	}
+	segID, blockID := w.block.identity()
+	return &writer{
+		block: w.block,
+		ts:    w.ts,
+		itemID: &GlobalItemID{
+			shardID:  w.series.shardID,
+			segID:    segID,
+			blockID:  blockID,
+			seriesID: w.series.seriesID,
+			id:       common.ItemID(uint64(w.ts.UnixNano())),
+		},
+		columns: w.values,
+	}, nil
+}
+
+func newWriterBuilder(seriesSpan *seriesSpan) WriterBuilder {
+	return &writerBuilder{
+		series:        seriesSpan,
+		seriesIDBytes: seriesSpan.seriesID.Marshal(),
+	}
+}
+
+var _ Writer = (*writer)(nil)
+
+type writer struct {
+	block   blockDelegate
+	ts      time.Time
+	columns []struct {
+		family []byte
+		val    []byte
+	}
+	itemID *GlobalItemID
+}
+
+func (w *writer) ItemID() GlobalItemID {
+	return *w.itemID
+}
+
+func (w *writer) WriteLSMIndex(field index.Field) error {
+	t := index.Term{
+		SeriesID:  w.itemID.seriesID,
+		IndexRule: string(field.Term),
+	}
+	field.Term = t.Marshal()
+	return w.block.writeLSMIndex(field, w.itemID.id)
+}
+
+func (w *writer) WriteInvertedIndex(field index.Field) error {
+	t := index.Term{
+		SeriesID:  w.itemID.seriesID,
+		IndexRule: string(field.Term),
+	}
+	field.Term = t.Marshal()
+	return w.block.writeInvertedIndex(field, w.itemID.id)
+}
+
+type dataBucket struct {
+	seriesID common.SeriesID
+	family   []byte
+}
+
+func (d dataBucket) marshal() []byte {
+	return bytes.Join([][]byte{
+		d.seriesID.Marshal(),
+		hash(d.family),
+	}, nil)
+}
+
+func (w *writer) Write() (id GlobalItemID, err error) {
+	for _, c := range w.columns {
+		err = w.block.write(dataBucket{
+			seriesID: w.itemID.seriesID,
+			family:   c.family,
+		}.marshal(),
+			c.val, w.ts)
+		if err != nil {
+			return id, err
+		}
+	}
+	return id, w.block.writePrimaryIndex(index.Field{
+		Term:  id.seriesID.Marshal(),
+		Value: convert.Int64ToBytes(w.ts.UnixNano()),
+	}, id.id)
+}
diff --git a/banyand/tsdb/seriesdb.go b/banyand/tsdb/seriesdb.go
index 8c0e9fb..24a622f 100644
--- a/banyand/tsdb/seriesdb.go
+++ b/banyand/tsdb/seriesdb.go
@@ -183,7 +183,7 @@ func newSeriesDataBase(ctx context.Context, shardID common.ShardID, path string,
 		return nil, logger.ErrNoLoggerInContext
 	}
 	if pl, ok := parentLogger.(*logger.Logger); ok {
-		sdb.l = pl.Named("series")
+		sdb.l = pl.Named("seriesSpan")
 	}
 	var err error
 	sdb.seriesMetadata, err = kv.OpenStore(0, path+"/md", kv.StoreWithNamedLogger("metadata", sdb.l))
diff --git a/banyand/tsdb/tsdb.go b/banyand/tsdb/tsdb.go
index 66ba915..0d3da45 100644
--- a/banyand/tsdb/tsdb.go
+++ b/banyand/tsdb/tsdb.go
@@ -30,12 +30,13 @@ import (
 	"go.uber.org/multierr"
 
 	"github.com/apache/skywalking-banyandb/api/common"
+	databasev2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v2"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 )
 
 const (
 	shardTemplate       = "%s/shard-%d"
-	seriesTemplate      = "%s/series"
+	seriesTemplate      = "%s/seriesSpan"
 	segTemplate         = "%s/seg-%s"
 	blockTemplate       = "%s/block-%s"
 	globalIndexTemplate = "%s/index"
@@ -47,11 +48,14 @@ const (
 )
 
 var ErrInvalidShardID = errors.New("invalid shard id")
+var indexRulesKey = contextIndexRulesKey{}
+
+type contextIndexRulesKey struct{}
 
 type Database interface {
 	io.Closer
 	Shards() []Shard
-	Shard(id uint) (Shard, error)
+	Shard(id common.ShardID) (Shard, error)
 }
 
 type Shard interface {
@@ -63,8 +67,9 @@ type Shard interface {
 var _ Database = (*database)(nil)
 
 type DatabaseOpts struct {
-	Location string
-	ShardNum uint32
+	Location   string
+	ShardNum   uint32
+	IndexRules []*databasev2.IndexRule
 }
 
 type database struct {
@@ -80,8 +85,8 @@ func (d *database) Shards() []Shard {
 	return d.sLst
 }
 
-func (d *database) Shard(id uint) (Shard, error) {
-	if id >= uint(len(d.sLst)) {
+func (d *database) Shard(id common.ShardID) (Shard, error) {
+	if uint(id) >= uint(len(d.sLst)) {
 		return nil, ErrInvalidShardID
 	}
 	return d.sLst[id], nil
@@ -115,6 +120,7 @@ func OpenDatabase(ctx context.Context, opts DatabaseOpts) (Database, error) {
 		return nil, errors.Wrap(err, "failed to read directory contents failed")
 	}
 	thisContext := context.WithValue(ctx, logger.ContextKey, db.logger)
+	thisContext = context.WithValue(thisContext, indexRulesKey, opts.IndexRules)
 	if len(entries) > 0 {
 		return loadDatabase(thisContext, db)
 	}
diff --git a/pkg/index/index.go b/pkg/index/index.go
new file mode 100644
index 0000000..46c70be
--- /dev/null
+++ b/pkg/index/index.go
@@ -0,0 +1,63 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package index
+
+import (
+	"bytes"
+
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+)
+
+type Field struct {
+	Term  []byte
+	Value []byte
+}
+
+func (f Field) Marshal() []byte {
+	return bytes.Join([][]byte{f.Term, f.Value}, nil)
+}
+
+type FieldSpec struct {
+	Name string
+}
+
+type RangeOpts struct {
+	Upper         []byte
+	Lower         []byte
+	IncludesUpper bool
+	IncludesLower bool
+}
+
+type FieldIterator interface {
+	Next() bool
+	Val() *PostingValue
+	Close() error
+}
+
+type PostingValue struct {
+	Key   []byte
+	Value posting.List
+}
+
+type Searcher interface {
+	MatchField(fieldName []byte) (list posting.List)
+	MatchTerms(field Field) (list posting.List)
+	Range(fieldName []byte, opts RangeOpts) (list posting.List)
+	FieldIterator(fieldName []byte, order modelv2.QueryOrder_Sort) FieldIterator
+}
diff --git a/banyand/index/tsdb/field_map.go b/pkg/index/inverted/field_map.go
similarity index 87%
copy from banyand/index/tsdb/field_map.go
copy to pkg/index/inverted/field_map.go
index 4d89713..f8d3789 100644
--- a/banyand/index/tsdb/field_map.go
+++ b/pkg/index/inverted/field_map.go
@@ -15,13 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package tsdb
+package inverted
 
 import (
 	"github.com/pkg/errors"
 
 	"github.com/apache/skywalking-banyandb/api/common"
 	"github.com/apache/skywalking-banyandb/pkg/convert"
+	"github.com/apache/skywalking-banyandb/pkg/index"
 )
 
 var ErrFieldAbsent = errors.New("field doesn't exist")
@@ -50,10 +51,10 @@ func (fm *fieldMap) get(key []byte) (*fieldValue, bool) {
 	return v, ok
 }
 
-func (fm *fieldMap) put(fv *Field, id common.ChunkID) error {
-	pm, ok := fm.get(fv.Name)
+func (fm *fieldMap) put(fv index.Field, id common.ItemID) error {
+	pm, ok := fm.get(fv.Term)
 	if !ok {
-		return errors.Wrapf(ErrFieldAbsent, "filed Name:%s", fv.Name)
+		return errors.Wrapf(ErrFieldAbsent, "filed Term:%s", fv.Term)
 	}
 	return pm.value.put(fv.Value, id)
 }
diff --git a/api/proto/banyandb/model/v2/write.proto b/pkg/index/inverted/inverted.go
similarity index 53%
rename from api/proto/banyandb/model/v2/write.proto
rename to pkg/index/inverted/inverted.go
index 4636bd3..d610d54 100644
--- a/api/proto/banyandb/model/v2/write.proto
+++ b/pkg/index/inverted/inverted.go
@@ -15,23 +15,38 @@
 // specific language governing permissions and limitations
 // under the License.
 
-syntax = "proto3";
+package inverted
 
-option java_package = "org.apache.skywalking.banyandb.model.v2";
-option go_package = "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2";
+import (
+	"github.com/apache/skywalking-banyandb/api/common"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+)
 
-package banyandb.model.v2;
+type GlobalStore interface {
+	Searcher() index.Searcher
+	Initialize(fields []index.FieldSpec) error
+	Insert(field index.Field, docID common.ItemID) error
+}
+
+type store struct {
+	memTable *MemTable
+	//TODO: add data tables
+}
+
+func (s *store) Searcher() index.Searcher {
+	return s.memTable
+}
+
+func (s *store) Initialize(fields []index.FieldSpec) error {
+	return s.memTable.Initialize(fields)
+}
 
-import "google/protobuf/struct.proto";
-import "banyandb/model/v2/common.proto";
+func (s *store) Insert(field index.Field, chunkID common.ItemID) error {
+	return s.memTable.Insert(field, chunkID)
+}
 
-message Tag {
-    oneof value_type {
-        google.protobuf.NullValue null = 1;
-        Str str = 2;
-        StrArray str_array = 3;
-        Int int = 4;
-        IntArray int_array = 5;
-        bytes binary_data = 6;
-    }
+func NewStore(name string) GlobalStore {
+	return &store{
+		memTable: NewMemTable(name),
+	}
 }
diff --git a/pkg/index/inverted/mem.go b/pkg/index/inverted/mem.go
new file mode 100644
index 0000000..e2db1f6
--- /dev/null
+++ b/pkg/index/inverted/mem.go
@@ -0,0 +1,155 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package inverted
+
+import (
+	"bytes"
+	"sort"
+
+	"github.com/pkg/errors"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting/roaring"
+)
+
+var ErrFieldsAbsent = errors.New("fields are absent")
+
+var _ index.Searcher = (*MemTable)(nil)
+
+type MemTable struct {
+	terms *fieldMap
+	name  string
+}
+
+func NewMemTable(name string) *MemTable {
+	return &MemTable{
+		name: name,
+	}
+}
+
+func (m *MemTable) Initialize(fields []index.FieldSpec) error {
+	if len(fields) < 1 {
+		return ErrFieldsAbsent
+	}
+	m.terms = newFieldMap(len(fields))
+	for _, f := range fields {
+		m.terms.createKey([]byte(f.Name))
+	}
+	return nil
+}
+
+func (m *MemTable) Insert(field index.Field, chunkID common.ItemID) error {
+	return m.terms.put(field, chunkID)
+}
+
+func (m *MemTable) MatchField(fieldName []byte) (list posting.List) {
+	fieldsValues, ok := m.terms.get(fieldName)
+	if !ok {
+		return roaring.EmptyPostingList
+	}
+	return fieldsValues.value.allValues()
+}
+
+var _ index.FieldIterator = (*fIterator)(nil)
+
+type fIterator struct {
+	index     int
+	val       *index.PostingValue
+	keys      [][]byte
+	valueRepo *postingMap
+	closed    bool
+}
+
+func (f *fIterator) Next() bool {
+	if f.closed {
+		return false
+	}
+	f.index++
+	if f.index >= len(f.keys) {
+		_ = f.Close()
+		return false
+	}
+	f.val = f.valueRepo.getEntry(f.keys[f.index])
+	if f.val == nil {
+		return f.Next()
+	}
+	return true
+}
+
+func (f *fIterator) Val() *index.PostingValue {
+	return f.val
+}
+
+func (f *fIterator) Close() error {
+	f.closed = true
+	return nil
+}
+
+func newFieldIterator(keys [][]byte, fValue *postingMap) index.FieldIterator {
+	return &fIterator{
+		keys:      keys,
+		valueRepo: fValue,
+		index:     -1,
+	}
+}
+
+func (m *MemTable) FieldIterator(fieldName []byte, order modelv2.QueryOrder_Sort) index.FieldIterator {
+	fieldsValues, ok := m.terms.get(fieldName)
+	if !ok {
+		return nil
+	}
+	fValue := fieldsValues.value
+	var keys [][]byte
+	{
+		fValue.mutex.RLock()
+		defer fValue.mutex.RUnlock()
+		for _, value := range fValue.repo {
+			keys = append(keys, value.Key)
+		}
+	}
+	switch order {
+	case modelv2.QueryOrder_SORT_ASC:
+		sort.SliceStable(keys, func(i, j int) bool {
+			return bytes.Compare(keys[i], keys[j]) < 0
+		})
+	case modelv2.QueryOrder_SORT_DESC:
+		sort.SliceStable(keys, func(i, j int) bool {
+			return bytes.Compare(keys[i], keys[j]) > 0
+		})
+	}
+	return newFieldIterator(keys, fValue)
+}
+
+func (m *MemTable) MatchTerms(field index.Field) (list posting.List) {
+	fieldsValues, ok := m.terms.get(field.Term)
+	if !ok {
+		return roaring.EmptyPostingList
+	}
+	return fieldsValues.value.get(field.Value).Clone()
+}
+
+func (m *MemTable) Range(fieldName []byte, opts index.RangeOpts) (list posting.List) {
+	fieldsValues, ok := m.terms.get(fieldName)
+	if !ok {
+		return roaring.EmptyPostingList
+	}
+	return fieldsValues.value.getRange(opts)
+}
diff --git a/pkg/index/inverted/mem_test.go b/pkg/index/inverted/mem_test.go
new file mode 100644
index 0000000..45117a7
--- /dev/null
+++ b/pkg/index/inverted/mem_test.go
@@ -0,0 +1,308 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package inverted
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/pkg/convert"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting/roaring"
+)
+
+func TestMemTable_Initialize(t *testing.T) {
+	type args struct {
+		fields []FieldSpec
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "golden path",
+			args: args{
+				fields: []FieldSpec{
+					{
+						Name: "service_name",
+					},
+					{
+						Name: "duration",
+					},
+				},
+			},
+		},
+		{
+			name:    "fields absent",
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			m := NewMemTable("sw")
+			var err error
+			if err = m.Initialize(tt.args.fields); (err != nil) != tt.wantErr {
+				t.Errorf("Initialize() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if err != nil {
+				return
+			}
+			assert.Equal(t, len(m.terms.repo), len(tt.args.fields))
+		})
+	}
+}
+
+func TestMemTable_Range(t *testing.T) {
+	type args struct {
+		fieldName []byte
+		opts      *RangeOpts
+	}
+	m := NewMemTable("sw")
+	setUp(t, m)
+	tests := []struct {
+		name     string
+		args     args
+		wantList posting.List
+	}{
+		{
+			name: "in range",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower: convert.Uint16ToBytes(100),
+					Upper: convert.Uint16ToBytes(500),
+				},
+			},
+			wantList: m.MatchTerms(&Field{
+				Name:  []byte("duration"),
+				Value: convert.Uint16ToBytes(200),
+			}),
+		},
+		{
+			name: "excludes edge",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower: convert.Uint16ToBytes(50),
+					Upper: convert.Uint16ToBytes(1000),
+				},
+			},
+			wantList: union(m,
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(200),
+				},
+			),
+		},
+		{
+			name: "includes lower",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower:         convert.Uint16ToBytes(50),
+					Upper:         convert.Uint16ToBytes(1000),
+					IncludesLower: true,
+				},
+			},
+			wantList: union(m,
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(50),
+				},
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(200),
+				},
+			),
+		},
+		{
+			name: "includes upper",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower:         convert.Uint16ToBytes(50),
+					Upper:         convert.Uint16ToBytes(1000),
+					IncludesUpper: true,
+				},
+			},
+			wantList: union(m,
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(200),
+				},
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(1000),
+				},
+			),
+		},
+		{
+			name: "includes edges",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower:         convert.Uint16ToBytes(50),
+					Upper:         convert.Uint16ToBytes(1000),
+					IncludesUpper: true,
+					IncludesLower: true,
+				},
+			},
+			wantList: union(m,
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(50),
+				},
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(200),
+				},
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(1000),
+				},
+			),
+		},
+		{
+			name: "match one",
+			args: args{
+				fieldName: []byte("duration"),
+				opts: &RangeOpts{
+					Lower:         convert.Uint16ToBytes(200),
+					Upper:         convert.Uint16ToBytes(200),
+					IncludesUpper: true,
+					IncludesLower: true,
+				},
+			},
+			wantList: union(m,
+				&Field{
+					Name:  []byte("duration"),
+					Value: convert.Uint16ToBytes(200),
+				},
+			),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if gotList := m.Range(tt.args.fieldName, tt.args.opts); !reflect.DeepEqual(gotList, tt.wantList) {
+				t.Errorf("Range() = %v, want %v", gotList.Len(), tt.wantList.Len())
+			}
+		})
+	}
+}
+
+func TestMemTable_Iterator(t *testing.T) {
+	tester := assert.New(t)
+	type args struct {
+		fieldName []byte
+		orderType modelv2.QueryOrder_Sort
+	}
+	m := NewMemTable("sw")
+	setUp(t, m)
+	tests := []struct {
+		name string
+		args args
+		want [][]byte
+	}{
+		{
+			name: "sort asc",
+			args: args{
+				fieldName: []byte("duration"),
+				orderType: modelv2.QueryOrder_SORT_ASC,
+			},
+			want: [][]byte{convert.Uint16ToBytes(50), convert.Uint16ToBytes(200), convert.Uint16ToBytes(1000)},
+		},
+		{
+			name: "sort desc",
+			args: args{
+				fieldName: []byte("duration"),
+				orderType: modelv2.QueryOrder_SORT_DESC,
+			},
+			want: [][]byte{convert.Uint16ToBytes(1000), convert.Uint16ToBytes(200), convert.Uint16ToBytes(50)},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			iter := m.FieldIterator(tt.args.fieldName, tt.args.orderType)
+			tester.NotNil(iter)
+			var got [][]byte
+			defer func() {
+				_ = iter.Close()
+			}()
+			for iter.Next() {
+				got = append(got, iter.Val().key)
+			}
+			tester.Equal(tt.want, got)
+		})
+	}
+}
+
+func union(memTable *MemTable, fields ...*Field) posting.List {
+	result := roaring.NewPostingList()
+	for _, f := range fields {
+		_ = result.Union(memTable.MatchTerms(f))
+	}
+	return result
+}
+
+func setUp(t *testing.T, mt *MemTable) {
+	assert.NoError(t, mt.Initialize([]FieldSpec{
+		{
+			Name: "service_name",
+		},
+		{
+			Name: "duration",
+		},
+	}))
+	for i := 0; i < 100; i++ {
+		if i%2 == 0 {
+			assert.NoError(t, mt.Insert(&Field{
+				Name:  []byte("service_name"),
+				Value: []byte("gateway"),
+			}, common.ItemID(i)))
+		} else {
+			assert.NoError(t, mt.Insert(&Field{
+				Name:  []byte("service_name"),
+				Value: []byte("webpage"),
+			}, common.ItemID(i)))
+		}
+	}
+	for i := 100; i < 200; i++ {
+		switch {
+		case i%3 == 0:
+			assert.NoError(t, mt.Insert(&Field{
+				Name:  []byte("duration"),
+				Value: convert.Uint16ToBytes(50),
+			}, common.ItemID(i)))
+		case i%3 == 1:
+			assert.NoError(t, mt.Insert(&Field{
+				Name:  []byte("duration"),
+				Value: convert.Uint16ToBytes(200),
+			}, common.ItemID(i)))
+		case i%3 == 2:
+			assert.NoError(t, mt.Insert(&Field{
+				Name:  []byte("duration"),
+				Value: convert.Uint16ToBytes(1000),
+			}, common.ItemID(i)))
+		}
+	}
+}
diff --git a/pkg/index/inverted/term_map.go b/pkg/index/inverted/term_map.go
new file mode 100644
index 0000000..c672239
--- /dev/null
+++ b/pkg/index/inverted/term_map.go
@@ -0,0 +1,161 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package inverted
+
+import (
+	"bytes"
+	"sort"
+	"sync"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	"github.com/apache/skywalking-banyandb/pkg/convert"
+	"github.com/apache/skywalking-banyandb/pkg/index"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting/roaring"
+)
+
+type termHashID uint64
+
+type postingMap struct {
+	repo  map[termHashID]*index.PostingValue
+	mutex sync.RWMutex
+}
+
+func newPostingMap() *postingMap {
+	return &postingMap{
+		repo: make(map[termHashID]*index.PostingValue),
+	}
+}
+
+func (p *postingMap) put(key []byte, id common.ItemID) error {
+	list := p.getOrCreate(key)
+	list.Insert(id)
+	return nil
+}
+
+func (p *postingMap) getOrCreate(key []byte) posting.List {
+	list := p.get(key)
+	if list != roaring.EmptyPostingList {
+		return list
+	}
+	p.mutex.Lock()
+	defer p.mutex.Unlock()
+	hashedKey := termHashID(convert.Hash(key))
+	v := &index.PostingValue{
+		Key:   key,
+		Value: roaring.NewPostingList(),
+	}
+	p.repo[hashedKey] = v
+	return v.Value
+}
+
+func (p *postingMap) get(key []byte) posting.List {
+	e := p.getEntry(key)
+	if e == nil {
+		return roaring.EmptyPostingList
+	}
+	return e.Value
+}
+
+func (p *postingMap) getEntry(key []byte) *index.PostingValue {
+	p.mutex.RLock()
+	defer p.mutex.RUnlock()
+	hashedKey := termHashID(convert.Hash(key))
+	v, ok := p.repo[hashedKey]
+	if !ok {
+		return nil
+	}
+	return v
+}
+
+func (p *postingMap) allValues() posting.List {
+	result := roaring.NewPostingList()
+	for _, value := range p.repo {
+		_ = result.Union(value.Value)
+	}
+	return result
+}
+
+func (p *postingMap) getRange(opts index.RangeOpts) posting.List {
+	switch bytes.Compare(opts.Upper, opts.Lower) {
+	case -1:
+		return roaring.EmptyPostingList
+	case 0:
+		if opts.IncludesUpper && opts.IncludesLower {
+			return p.get(opts.Upper)
+		}
+		return roaring.EmptyPostingList
+	}
+	p.mutex.RLock()
+	defer p.mutex.RUnlock()
+	keys := make(Asc, 0, len(p.repo))
+	for _, v := range p.repo {
+		keys = append(keys, v.Key)
+	}
+	sort.Sort(keys)
+	index := sort.Search(len(keys), func(i int) bool {
+		return bytes.Compare(keys[i], opts.Lower) >= 0
+	})
+	result := roaring.NewPostingList()
+	for i := index; i < len(keys); i++ {
+		k := keys[i]
+		switch {
+		case bytes.Equal(k, opts.Lower):
+			if opts.IncludesLower {
+				_ = result.Union(p.repo[termHashID(convert.Hash(k))].Value)
+			}
+		case bytes.Compare(k, opts.Upper) > 0:
+			break
+		case bytes.Equal(k, opts.Upper):
+			if opts.IncludesUpper {
+				_ = result.Union(p.repo[termHashID(convert.Hash(k))].Value)
+			}
+		default:
+			_ = result.Union(p.repo[termHashID(convert.Hash(k))].Value)
+		}
+	}
+	return result
+}
+
+type Asc [][]byte
+
+func (a Asc) Len() int {
+	return len(a)
+}
+
+func (a Asc) Less(i, j int) bool {
+	return bytes.Compare(a[i], a[j]) < 0
+}
+
+func (a Asc) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+type Desc [][]byte
+
+func (d Desc) Len() int {
+	return len(d)
+}
+
+func (d Desc) Less(i, j int) bool {
+	return bytes.Compare(d[i], d[j]) > 0
+}
+
+func (d Desc) Swap(i, j int) {
+	d[i], d[j] = d[j], d[i]
+}
diff --git a/banyand/index/tsdb/field_map.go b/pkg/index/posting/posting.go
similarity index 51%
copy from banyand/index/tsdb/field_map.go
copy to pkg/index/posting/posting.go
index 4d89713..593e61c 100644
--- a/banyand/index/tsdb/field_map.go
+++ b/pkg/index/posting/posting.go
@@ -15,50 +15,61 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package tsdb
+package posting
 
 import (
 	"github.com/pkg/errors"
 
 	"github.com/apache/skywalking-banyandb/api/common"
-	"github.com/apache/skywalking-banyandb/pkg/convert"
 )
 
-var ErrFieldAbsent = errors.New("field doesn't exist")
+var ErrListEmpty = errors.New("postings list is empty")
 
-type fieldHashID uint64
+// List is a collection of common.ItemID.
+type List interface {
+	Contains(id common.ItemID) bool
 
-type fieldMap struct {
-	repo map[fieldHashID]*fieldValue
-}
+	IsEmpty() bool
 
-func newFieldMap(initialSize int) *fieldMap {
-	return &fieldMap{
-		repo: make(map[fieldHashID]*fieldValue, initialSize),
-	}
-}
+	Max() (common.ItemID, error)
 
-func (fm *fieldMap) createKey(key []byte) {
-	fm.repo[fieldHashID(convert.Hash(key))] = &fieldValue{
-		key:   key,
-		value: newPostingMap(),
-	}
-}
+	Len() int
 
-func (fm *fieldMap) get(key []byte) (*fieldValue, bool) {
-	v, ok := fm.repo[fieldHashID(convert.Hash(key))]
-	return v, ok
-}
+	Iterator() Iterator
+
+	Clone() List
+
+	Equal(other List) bool
+
+	Insert(i common.ItemID)
+
+	Intersect(other List) error
+
+	Difference(other List) error
+
+	Union(other List) error
 
-func (fm *fieldMap) put(fv *Field, id common.ChunkID) error {
-	pm, ok := fm.get(fv.Name)
-	if !ok {
-		return errors.Wrapf(ErrFieldAbsent, "filed Name:%s", fv.Name)
-	}
-	return pm.value.put(fv.Value, id)
+	UnionMany(others []List) error
+
+	AddIterator(iter Iterator) error
+
+	AddRange(min, max common.ItemID) error
+
+	RemoveRange(min, max common.ItemID) error
+
+	Reset()
+
+	ToSlice() []common.ItemID
+
+	Marshall() ([]byte, error)
+
+	Unmarshall(data []byte) error
 }
 
-type fieldValue struct {
-	key   []byte
-	value *postingMap
+type Iterator interface {
+	Next() bool
+
+	Current() common.ItemID
+
+	Close() error
 }
diff --git a/pkg/index/posting/roaring/roaring.go b/pkg/index/posting/roaring/roaring.go
new file mode 100644
index 0000000..8f1f3d7
--- /dev/null
+++ b/pkg/index/posting/roaring/roaring.go
@@ -0,0 +1,214 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package roaring
+
+import (
+	"github.com/RoaringBitmap/roaring/roaring64"
+	"github.com/pkg/errors"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+)
+
+var (
+	EmptyPostingList = NewPostingList()
+
+	ErrIntersectRoaringOnly  = errors.New("Intersect only supported between roaringDocId sets")
+	ErrUnionRoaringOnly      = errors.New("Union only supported between roaringDocId sets")
+	ErrDifferenceRoaringOnly = errors.New("Difference only supported between roaringDocId sets")
+)
+
+var _ posting.List = (*postingsList)(nil)
+
+// postingsList abstracts a Roaring Bitmap.
+type postingsList struct {
+	bitmap *roaring64.Bitmap
+}
+
+func (p *postingsList) Marshall() ([]byte, error) {
+	return p.bitmap.MarshalBinary()
+}
+
+func (p *postingsList) Unmarshall(data []byte) error {
+	return p.bitmap.UnmarshalBinary(data)
+}
+
+func NewPostingList() posting.List {
+	return &postingsList{
+		bitmap: roaring64.New(),
+	}
+}
+
+func NewPostingListWithInitialData(data ...uint64) posting.List {
+	list := NewPostingList()
+	for _, d := range data {
+		list.Insert(common.ItemID(d))
+	}
+	return list
+}
+
+func (p *postingsList) Contains(id common.ItemID) bool {
+	return p.bitmap.Contains(uint64(id))
+}
+
+func (p *postingsList) IsEmpty() bool {
+	return p.bitmap.IsEmpty()
+}
+
+func (p *postingsList) Max() (common.ItemID, error) {
+	if p.IsEmpty() {
+		return 0, posting.ErrListEmpty
+	}
+	return common.ItemID(p.bitmap.Maximum()), nil
+}
+
+func (p *postingsList) Len() int {
+	return int(p.bitmap.GetCardinality())
+}
+
+func (p *postingsList) Iterator() posting.Iterator {
+	return &roaringIterator{
+		iter: p.bitmap.Iterator(),
+	}
+}
+
+func (p *postingsList) Clone() posting.List {
+	return &postingsList{
+		bitmap: p.bitmap.Clone(),
+	}
+}
+
+func (p *postingsList) Equal(other posting.List) bool {
+	if p.Len() != other.Len() {
+		return false
+	}
+
+	iter := p.Iterator()
+	otherIter := other.Iterator()
+
+	for iter.Next() {
+		if !otherIter.Next() {
+			return false
+		}
+		if iter.Current() != otherIter.Current() {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (p *postingsList) Insert(id common.ItemID) {
+	p.bitmap.Add(uint64(id))
+}
+
+func (p *postingsList) Intersect(other posting.List) error {
+	o, ok := other.(*postingsList)
+	if !ok {
+		return ErrIntersectRoaringOnly
+	}
+	p.bitmap.And(o.bitmap)
+	return nil
+}
+
+func (p *postingsList) Difference(other posting.List) error {
+	o, ok := other.(*postingsList)
+	if !ok {
+		return ErrDifferenceRoaringOnly
+	}
+	p.bitmap.AndNot(o.bitmap)
+	return nil
+}
+
+func (p *postingsList) Union(other posting.List) error {
+	o, ok := other.(*postingsList)
+	if !ok {
+		return ErrUnionRoaringOnly
+	}
+	p.bitmap.Or(o.bitmap)
+	return nil
+}
+
+func (p *postingsList) UnionMany(others []posting.List) error {
+	for _, other := range others {
+		err := p.Union(other)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (p *postingsList) AddIterator(iter posting.Iterator) error {
+	for iter.Next() {
+		p.Insert(iter.Current())
+	}
+	return nil
+}
+
+func (p *postingsList) AddRange(min, max common.ItemID) error {
+	for i := min; i < max; i++ {
+		p.bitmap.Add(uint64(i))
+	}
+	return nil
+}
+
+func (p *postingsList) RemoveRange(min, max common.ItemID) error {
+	for i := min; i < max; i++ {
+		p.bitmap.Remove(uint64(i))
+	}
+	return nil
+}
+
+func (p *postingsList) Reset() {
+	p.bitmap.Clear()
+}
+
+type roaringIterator struct {
+	iter    roaring64.IntIterable64
+	current common.ItemID
+	closed  bool
+}
+
+func (it *roaringIterator) Current() common.ItemID {
+	return it.current
+}
+
+func (it *roaringIterator) Next() bool {
+	if it.closed || !it.iter.HasNext() {
+		return false
+	}
+	v := it.iter.Next()
+	it.current = common.ItemID(v)
+	return true
+}
+
+func (it *roaringIterator) Close() error {
+	it.closed = true
+	return nil
+}
+
+func (p *postingsList) ToSlice() []common.ItemID {
+	iter := p.Iterator()
+	defer iter.Close()
+	s := make([]common.ItemID, 0, p.Len())
+	for iter.Next() {
+		s = append(s, iter.Current())
+	}
+	return s
+}
diff --git a/pkg/index/search.go b/pkg/index/search.go
new file mode 100644
index 0000000..3fa8bd4
--- /dev/null
+++ b/pkg/index/search.go
@@ -0,0 +1,335 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package index
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"strings"
+
+	"github.com/pkg/errors"
+
+	"github.com/apache/skywalking-banyandb/api/common"
+	modelv2 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v2"
+	"github.com/apache/skywalking-banyandb/pkg/index/posting"
+)
+
+var ErrNotRangeOperation = errors.New("this is not an range operation")
+
+type Executor interface {
+	Execute() (posting.List, error)
+}
+
+type Tree interface {
+	Executor
+}
+
+type Term struct {
+	SeriesID  common.SeriesID
+	IndexRule string
+}
+
+func (t *Term) Marshal() []byte {
+	return bytes.Join([][]byte{
+		t.SeriesID.Marshal(),
+		[]byte(t.IndexRule),
+	}, []byte(":"))
+}
+
+type Condition map[Term][]ConditionValue
+
+type ConditionValue struct {
+	Values [][]byte
+	Op     modelv2.Condition_BinaryOp
+}
+
+func BuildTree(searcher Searcher, condMap Condition) (Tree, error) {
+	root := &andNode{
+		node: &node{
+			SubNodes: make([]Executor, 0),
+			searcher: searcher,
+		},
+	}
+	for term, conds := range condMap {
+		key := term.Marshal()
+		var rangeLeaf *rangeOp
+		for _, cond := range conds {
+			if rangeLeaf != nil && !rangeOP(cond.Op) {
+				return nil, errors.Wrapf(ErrNotRangeOperation, "op:%s", cond.Op.String())
+			}
+			if rangeOP(cond.Op) {
+				if rangeLeaf == nil {
+					rangeLeaf = root.addRangeLeaf(key)
+				}
+				opts := rangeLeaf.Opts
+				switch cond.Op {
+				case modelv2.Condition_BINARY_OP_GT:
+					opts.Lower = bytes.Join(cond.Values, nil)
+				case modelv2.Condition_BINARY_OP_GE:
+					opts.Lower = bytes.Join(cond.Values, nil)
+					opts.IncludesLower = true
+				case modelv2.Condition_BINARY_OP_LT:
+					opts.Upper = bytes.Join(cond.Values, nil)
+				case modelv2.Condition_BINARY_OP_LE:
+					opts.Upper = bytes.Join(cond.Values, nil)
+					opts.IncludesUpper = true
+				}
+				continue
+			}
+			switch cond.Op {
+			case modelv2.Condition_BINARY_OP_EQ:
+				root.addEq(key, cond.Values)
+			case modelv2.Condition_BINARY_OP_NE:
+				root.addNot(key, root.newEq(key, cond.Values))
+			case modelv2.Condition_BINARY_OP_HAVING:
+				n := root.addOrNode(len(cond.Values))
+				for _, v := range cond.Values {
+					n.addEq(key, [][]byte{v})
+				}
+			case modelv2.Condition_BINARY_OP_NOT_HAVING:
+				n := root.newOrNode(len(cond.Values))
+				for _, v := range cond.Values {
+					n.addEq(key, [][]byte{v})
+				}
+				root.addNot(key, n)
+			}
+		}
+	}
+	return root, nil
+}
+
+func rangeOP(op modelv2.Condition_BinaryOp) bool {
+	switch op {
+	case modelv2.Condition_BINARY_OP_GT,
+		modelv2.Condition_BINARY_OP_GE,
+		modelv2.Condition_BINARY_OP_LT,
+		modelv2.Condition_BINARY_OP_LE:
+		return true
+	}
+	return false
+}
+
+type logicalOP interface {
+	Executor
+	merge(posting.List) error
+}
+
+type node struct {
+	searcher Searcher
+	value    posting.List
+	SubNodes []Executor `json:"sub_nodes,omitempty"`
+}
+
+func (n *node) newEq(key []byte, values [][]byte) *eq {
+	return &eq{
+		leaf: &leaf{
+			Key:      key,
+			Values:   values,
+			searcher: n.searcher,
+		},
+	}
+}
+
+func (n *node) addEq(key []byte, values [][]byte) {
+	n.SubNodes = append(n.SubNodes, n.newEq(key, values))
+}
+
+func (n *node) addNot(key []byte, inner Executor) {
+	n.SubNodes = append(n.SubNodes, &not{
+		Key:      key,
+		searcher: n.searcher,
+		Inner:    inner,
+	})
+}
+
+func (n *node) addRangeLeaf(key []byte) *rangeOp {
+	r := &rangeOp{
+		leaf: &leaf{
+			Key:      key,
+			searcher: n.searcher,
+		},
+		Opts: &RangeOpts{},
+	}
+	n.SubNodes = append(n.SubNodes, r)
+	return r
+}
+
+func (n *node) newOrNode(size int) *orNode {
+	return &orNode{
+		node: &node{
+			searcher: n.searcher,
+			SubNodes: make([]Executor, 0, size),
+		},
+	}
+}
+
+func (n *node) addOrNode(size int) *orNode {
+	on := n.newOrNode(size)
+	n.SubNodes = append(n.SubNodes, on)
+	return on
+}
+
+func (n *node) pop() (Executor, bool) {
+	if len(n.SubNodes) < 1 {
+		return nil, false
+	}
+	sn := n.SubNodes[0]
+	n.SubNodes = n.SubNodes[1:]
+	return sn, true
+}
+
+func execute(n *node, lp logicalOP) (posting.List, error) {
+	ex, hasNext := n.pop()
+	if !hasNext {
+		return n.value, nil
+	}
+	r, err := ex.Execute()
+	if err != nil {
+		return nil, err
+	}
+	if n.value == nil {
+		n.value = r
+		return lp.Execute()
+	}
+	err = lp.merge(r)
+	if err != nil {
+		return nil, err
+	}
+	if n.value.IsEmpty() {
+		return n.value, nil
+	}
+	return lp.Execute()
+}
+
+type andNode struct {
+	*node
+}
+
+func (an *andNode) merge(list posting.List) error {
+	return an.value.Intersect(list)
+}
+
+func (an *andNode) Execute() (posting.List, error) {
+	return execute(an.node, an)
+}
+
+func (an *andNode) MarshalJSON() ([]byte, error) {
+	data := make(map[string]interface{}, 1)
+	data["and"] = an.node.SubNodes
+	return json.Marshal(data)
+}
+
+type orNode struct {
+	*node
+}
+
+func (on *orNode) merge(list posting.List) error {
+	return on.value.Union(list)
+}
+
+func (on *orNode) Execute() (posting.List, error) {
+	return execute(on.node, on)
+}
+
+func (on *orNode) MarshalJSON() ([]byte, error) {
+	data := make(map[string]interface{}, 1)
+	data["or"] = on.node.SubNodes
+	return json.Marshal(data)
+}
+
+type leaf struct {
+	Executor
+	Key      []byte
+	Values   [][]byte
+	searcher Searcher
+}
+
+type not struct {
+	Executor
+	Key      []byte
+	searcher Searcher
+	Inner    Executor
+}
+
+func (n *not) Execute() (posting.List, error) {
+	all := n.searcher.MatchField([]byte(n.Key))
+	list, err := n.Inner.Execute()
+	if err != nil {
+		return nil, err
+	}
+	err = all.Difference(list)
+	return all, err
+}
+
+func (n *not) MarshalJSON() ([]byte, error) {
+	data := make(map[string]interface{}, 1)
+	data["not"] = n.Inner
+	return json.Marshal(data)
+}
+
+type eq struct {
+	*leaf
+}
+
+func (eq *eq) Execute() (posting.List, error) {
+	return eq.searcher.MatchTerms(Field{
+		Term:  []byte(eq.Key),
+		Value: bytes.Join(eq.Values, nil),
+	}), nil
+}
+
+func (eq *eq) MarshalJSON() ([]byte, error) {
+	data := make(map[string]interface{}, 1)
+	data["eq"] = eq.leaf
+	return json.Marshal(data)
+}
+
+type rangeOp struct {
+	*leaf
+	Opts *RangeOpts
+}
+
+func (r *rangeOp) Execute() (posting.List, error) {
+	return r.searcher.Range(r.Key, *r.Opts), nil
+}
+
+func (r *rangeOp) MarshalJSON() ([]byte, error) {
+	data := make(map[string]interface{}, 1)
+	var builder strings.Builder
+	if r.Opts.Lower != nil {
+		if r.Opts.IncludesLower {
+			builder.WriteString("[")
+		} else {
+			builder.WriteString("(")
+		}
+	}
+	builder.WriteString(base64.StdEncoding.EncodeToString(r.Opts.Lower))
+	builder.WriteString(",")
+	builder.WriteString(base64.StdEncoding.EncodeToString(r.Opts.Upper))
+	if r.Opts.Upper != nil {
+		if r.Opts.IncludesUpper {
+			builder.WriteString("]")
+		} else {
+			builder.WriteString(")")
+		}
+	}
+	data["key"] = r.Key
+	data["range"] = builder.String()
+	return json.Marshal(data)
+}