@@ -7,6 +7,8 @@ package datasets.v1;
77import "buf/validate/validate.proto" ;
88import "datasets/v1/core.proto" ;
99import "datasets/v1/well_known_types.proto" ;
10+ import "google/protobuf/duration.proto" ;
11+ import "google/protobuf/timestamp.proto" ;
1012import "tilebox/v1/id.proto" ;
1113import "tilebox/v1/query.proto" ;
1214
@@ -41,6 +43,125 @@ message QueryFilters {
4143 tilebox.v1.IDInterval datapoint_interval = 2 ;
4244
4345 SpatialFilter spatial_extent = 3 ;
46+
47+ // Additional expressions over fields marked queryable in the dataset schema.
48+ // All top-level expressions are combined with each other and with the interval and spatial filters using logical AND.
49+ // Use a nested LogicalExpression for explicit OR, NOT, or grouped boolean logic.
50+ repeated FilterExpression expressions = 4 [(buf.validate.field ) .repeated.max_items = 100 ];
51+ }
52+
53+ // FilterExpression is a typed filter expression compatible with the Basic CQL2 property-to-literal subset.
54+ message FilterExpression {
55+ option (buf.validate.message ).oneof = {
56+ fields : [
57+ "logical" ,
58+ "comparison",
59+ "is_null"
60+ ]
61+ required : true
62+ };
63+
64+ // Exactly one expression node must be set.
65+ LogicalExpression logical = 1 ;
66+ FieldComparison comparison = 2 ;
67+ FieldNullCheck is_null = 3 ;
68+ }
69+
70+ // LogicalExpression combines nested filter expressions.
71+ message LogicalExpression {
72+ option (buf.validate.message ).cel = {
73+ id : "logical_expression.operand_arity"
74+ message : "NOT requires exactly one operand; AND and OR require at least two"
75+ expression : "this.operator == 3 ? this.operands.size() == 1 : this.operands.size() >= 2"
76+ };
77+
78+ // The logical operation to apply to operands.
79+ LogicalOperator operator = 1 [(buf.validate.field ).enum = {
80+ defined_only : true
81+ not_in : [0 ]
82+ }];
83+ // Operands for the logical operation. AND and OR require at least two; NOT requires exactly one.
84+ repeated FilterExpression operands = 2 [(buf.validate.field ).repeated = {
85+ min_items : 1
86+ max_items : 100
87+ }];
88+ }
89+
90+ // LogicalOperator specifies how nested filter expressions are combined.
91+ enum LogicalOperator {
92+ LOGICAL_OPERATOR_UNSPECIFIED = 0 ;
93+ LOGICAL_OPERATOR_AND = 1 ;
94+ LOGICAL_OPERATOR_OR = 2 ;
95+ LOGICAL_OPERATOR_NOT = 3 ;
96+ }
97+
98+ // FieldComparison compares a queryable dataset field to a typed literal value.
99+ message FieldComparison {
100+ // Name of a top-level field in the dataset schema.
101+ string field_name = 1 [(buf.validate.field ).string = {
102+ min_len : 1
103+ max_len : 100
104+ pattern : "^[a-z][a-z0-9_]*$"
105+ }];
106+ // Comparison operation to apply.
107+ FieldComparisonOperator operator = 2 [(buf.validate.field ).enum = {
108+ defined_only : true
109+ not_in : [0 ]
110+ }];
111+ // Literal value. Its kind must match the field's protobuf descriptor.
112+ FieldQueryValue value = 3 [(buf.validate.field ).required = true ];
113+ }
114+
115+ // FieldComparisonOperator specifies a Basic CQL2 comparison operation.
116+ enum FieldComparisonOperator {
117+ FIELD_COMPARISON_OPERATOR_UNSPECIFIED = 0 ;
118+ FIELD_COMPARISON_OPERATOR_EQUAL = 1 ;
119+ FIELD_COMPARISON_OPERATOR_NOT_EQUAL = 2 ;
120+ FIELD_COMPARISON_OPERATOR_LESS_THAN = 3 ;
121+ FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL = 4 ;
122+ FIELD_COMPARISON_OPERATOR_GREATER_THAN = 5 ;
123+ FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL = 6 ;
124+ }
125+
126+ // FieldNullCheck matches datapoints for which a queryable field is absent or explicitly null.
127+ // IS NOT NULL is represented by wrapping this expression in LOGICAL_OPERATOR_NOT.
128+ message FieldNullCheck {
129+ // Name of a top-level field in the dataset schema.
130+ string field_name = 1 [(buf.validate.field ).string = {
131+ min_len : 1
132+ max_len : 100
133+ pattern : "^[a-z][a-z0-9_]*$"
134+ }];
135+ }
136+
137+ // FieldQueryValue is a typed scalar or well-known-type literal used in a field comparison.
138+ message FieldQueryValue {
139+ option (buf.validate.message ).oneof = {
140+ fields : [
141+ "bool_value" ,
142+ "int64_value",
143+ "uint64_value",
144+ "double_value",
145+ "string_value",
146+ "timestamp_value",
147+ "duration_value",
148+ "enum_name",
149+ "bytes_value"
150+ ]
151+ required : true
152+ };
153+
154+ // Exactly one typed literal must be set. Explicit presence preserves zero and empty values.
155+ bool bool_value = 1 [features.field_presence = EXPLICIT ];
156+ int64 int64_value = 2 [features.field_presence = EXPLICIT ];
157+ uint64 uint64_value = 3 [features.field_presence = EXPLICIT ];
158+ double double_value = 4 [features.field_presence = EXPLICIT ];
159+ string string_value = 5 [features.field_presence = EXPLICIT ];
160+ google.protobuf.Timestamp timestamp_value = 6 ;
161+ google.protobuf.Duration duration_value = 7 ;
162+ // Symbolic value from the field's enum descriptor.
163+ string enum_name = 8 [features.field_presence = EXPLICIT ];
164+ bytes bytes_value = 9 [features.field_presence = EXPLICIT ];
44165}
45166
46167// SpatialFilterMode specifies how geometries are compared to a given spatial filter.
0 commit comments