1
+ use std:: fmt:: Formatter ;
2
+
1
3
use arrow:: datatypes:: { DataType , Field } ;
2
4
use datafusion:: common:: Column ;
3
- use datafusion:: logical_expr:: { Expr , col, lit} ;
5
+ use datafusion:: logical_expr:: { Expr , col, lit, not } ;
4
6
use datafusion:: prelude:: { array_element, array_has, array_sort} ;
7
+ use strum:: VariantArray as _;
5
8
6
9
use re_ui:: UiExt as _;
7
10
use re_ui:: syntax_highlighting:: SyntaxHighlightedBuilder ;
@@ -11,8 +14,9 @@ use super::{FilterError, FilterUiAction};
11
14
/// Filter for non-nullable boolean columns.
12
15
///
13
16
/// This represents both the filter itself, and the state of the corresponding UI.
14
- #[ derive( Debug , Clone , PartialEq , Eq ) ]
17
+ #[ derive( Debug , Clone , Default , PartialEq , Eq ) ]
15
18
pub enum NonNullableBooleanFilter {
19
+ #[ default]
16
20
IsTrue ,
17
21
IsFalse ,
18
22
}
@@ -52,7 +56,11 @@ impl NonNullableBooleanFilter {
52
56
}
53
57
54
58
pub fn popup_ui ( & mut self , ui : & mut egui:: Ui , column_name : & str ) -> FilterUiAction {
55
- popup_header_ui ( ui, column_name) ;
59
+ ui. label (
60
+ SyntaxHighlightedBuilder :: body_default ( column_name)
61
+ . with_keyword ( " is" )
62
+ . into_widget_text ( ui. style ( ) ) ,
63
+ ) ;
56
64
57
65
let mut clicked = false ;
58
66
@@ -72,83 +80,192 @@ impl NonNullableBooleanFilter {
72
80
}
73
81
}
74
82
75
- /// Filter for nullable boolean columns.
76
- ///
77
- /// This represents both the filter itself, and the state of the corresponding UI.
78
- #[ derive( Debug , Clone , PartialEq , Eq ) ]
83
+ #[ derive( Debug , Clone , Copy , Default , PartialEq , Eq , strum:: VariantArray ) ]
79
84
#[ expect( clippy:: enum_variant_names) ]
80
- pub enum NullableBooleanFilter {
85
+ pub enum NullableBooleanValue {
86
+ #[ default]
81
87
IsTrue ,
82
88
IsFalse ,
83
89
IsNull ,
84
90
}
85
91
86
- impl NullableBooleanFilter {
92
+ impl NullableBooleanValue {
87
93
pub fn as_bool ( & self ) -> Option < bool > {
88
94
match self {
89
95
Self :: IsTrue => Some ( true ) ,
90
96
Self :: IsFalse => Some ( false ) ,
91
97
Self :: IsNull => None ,
92
98
}
93
99
}
100
+ }
101
+
102
+ #[ derive( Debug , Clone , Copy , Default , PartialEq , Eq , strum:: VariantArray ) ]
103
+ pub enum NullableBooleanOperator {
104
+ #[ default]
105
+ Is ,
106
+ IsNot ,
107
+ }
108
+
109
+ impl std:: fmt:: Display for NullableBooleanOperator {
110
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
111
+ match self {
112
+ Self :: Is => "is" . fmt ( f) ,
113
+ Self :: IsNot => "is not" . fmt ( f) ,
114
+ }
115
+ }
116
+ }
117
+
118
+ /// Filter for nullable boolean columns.
119
+ ///
120
+ /// This represents both the filter itself, and the state of the corresponding UI.
121
+ #[ derive( Clone , Default , PartialEq , Eq ) ]
122
+ pub struct NullableBooleanFilter {
123
+ value : NullableBooleanValue ,
124
+ operator : NullableBooleanOperator ,
125
+ }
126
+
127
+ // to make snapshot more compact
128
+ impl std:: fmt:: Debug for NullableBooleanFilter {
129
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
130
+ let op = match self . operator {
131
+ NullableBooleanOperator :: Is => "" ,
132
+ NullableBooleanOperator :: IsNot => "not " ,
133
+ } ;
134
+
135
+ f. write_str ( & format ! ( "NullableBooleanFilter({op}{:?})" , self . value) )
136
+ }
137
+ }
138
+
139
+ impl NullableBooleanFilter {
140
+ pub fn new_is_true ( ) -> Self {
141
+ Self {
142
+ value : NullableBooleanValue :: IsTrue ,
143
+ operator : NullableBooleanOperator :: Is ,
144
+ }
145
+ }
146
+
147
+ pub fn new_is_false ( ) -> Self {
148
+ Self {
149
+ value : NullableBooleanValue :: IsFalse ,
150
+ operator : NullableBooleanOperator :: Is ,
151
+ }
152
+ }
153
+
154
+ pub fn new_is_null ( ) -> Self {
155
+ Self {
156
+ value : NullableBooleanValue :: IsNull ,
157
+ operator : NullableBooleanOperator :: Is ,
158
+ }
159
+ }
160
+
161
+ pub fn with_is_not ( mut self ) -> Self {
162
+ self . operator = NullableBooleanOperator :: IsNot ;
163
+ self
164
+ }
94
165
95
166
pub fn as_filter_expression (
96
167
& self ,
97
168
column : & Column ,
98
169
field : & Field ,
99
170
) -> Result < Expr , FilterError > {
100
- match field. data_type ( ) {
171
+ let expr = match field. data_type ( ) {
101
172
DataType :: Boolean => {
102
- if let Some ( value) = self . as_bool ( ) {
103
- Ok ( col ( column. clone ( ) ) . eq ( lit ( value) ) )
173
+ if let Some ( value) = self . value . as_bool ( ) {
174
+ col ( column. clone ( ) ) . eq ( lit ( value) )
104
175
} else {
105
- Ok ( col ( column. clone ( ) ) . is_null ( ) )
176
+ col ( column. clone ( ) ) . is_null ( )
106
177
}
107
178
}
108
179
109
180
DataType :: List ( field) | DataType :: ListView ( field)
110
181
if field. data_type ( ) == & DataType :: Boolean =>
111
182
{
112
183
// `ANY` semantics
113
- if let Some ( value) = self . as_bool ( ) {
114
- Ok ( array_has ( col ( column. clone ( ) ) , lit ( value) ) )
184
+ if let Some ( value) = self . value . as_bool ( ) {
185
+ array_has ( col ( column. clone ( ) ) , lit ( value) )
115
186
} else {
116
- Ok ( col ( column. clone ( ) ) . is_null ( ) . or ( array_element (
187
+ col ( column. clone ( ) ) . is_null ( ) . or ( array_element (
117
188
array_sort ( col ( column. clone ( ) ) , lit ( "ASC" ) , lit ( "NULLS FIRST" ) ) ,
118
189
lit ( 1 ) ,
119
190
)
120
- . is_null ( ) ) )
191
+ . is_null ( ) )
121
192
}
122
193
}
123
194
124
- _ => Err ( FilterError :: InvalidNullableBooleanFilter (
125
- self . clone ( ) ,
126
- field. clone ( ) . into ( ) ,
127
- ) ) ,
195
+ _ => {
196
+ return Err ( FilterError :: InvalidNullableBooleanFilter (
197
+ self . clone ( ) ,
198
+ field. clone ( ) . into ( ) ,
199
+ ) ) ;
200
+ }
201
+ } ;
202
+
203
+ match self . operator {
204
+ NullableBooleanOperator :: Is => Ok ( expr) ,
205
+ NullableBooleanOperator :: IsNot => Ok ( not ( expr. clone ( ) ) . or ( expr. is_null ( ) ) ) ,
128
206
}
129
207
}
130
208
131
209
pub fn operand_text ( & self ) -> String {
132
- if let Some ( value) = self . as_bool ( ) {
210
+ if let Some ( value) = self . value . as_bool ( ) {
133
211
value. to_string ( )
134
212
} else {
135
213
"null" . to_owned ( )
136
214
}
137
215
}
138
216
217
+ pub fn operator ( & self ) -> NullableBooleanOperator {
218
+ self . operator
219
+ }
220
+
139
221
pub fn popup_ui ( & mut self , ui : & mut egui:: Ui , column_name : & str ) -> FilterUiAction {
140
- popup_header_ui ( ui, column_name) ;
222
+ ui. horizontal ( |ui| {
223
+ ui. label (
224
+ SyntaxHighlightedBuilder :: body_default ( column_name) . into_widget_text ( ui. style ( ) ) ,
225
+ ) ;
226
+
227
+ egui:: ComboBox :: new ( "null_bool_op" , "" )
228
+ . selected_text (
229
+ SyntaxHighlightedBuilder :: keyword ( & self . operator . to_string ( ) )
230
+ . into_widget_text ( ui. style ( ) ) ,
231
+ )
232
+ . show_ui ( ui, |ui| {
233
+ for possible_op in NullableBooleanOperator :: VARIANTS {
234
+ if ui
235
+ . button (
236
+ SyntaxHighlightedBuilder :: keyword ( & possible_op. to_string ( ) )
237
+ . into_widget_text ( ui. style ( ) ) ,
238
+ )
239
+ . clicked ( )
240
+ {
241
+ self . operator = * possible_op;
242
+ }
243
+ }
244
+ } ) ;
245
+ } ) ;
141
246
142
247
let mut clicked = false ;
143
248
144
249
clicked |= ui
145
- . re_radio_value ( self , Self :: IsTrue , primitive_widget_text ( ui, "true" ) )
250
+ . re_radio_value (
251
+ & mut self . value ,
252
+ NullableBooleanValue :: IsTrue ,
253
+ primitive_widget_text ( ui, "true" ) ,
254
+ )
146
255
. clicked ( ) ;
147
256
clicked |= ui
148
- . re_radio_value ( self , Self :: IsFalse , primitive_widget_text ( ui, "false" ) )
257
+ . re_radio_value (
258
+ & mut self . value ,
259
+ NullableBooleanValue :: IsFalse ,
260
+ primitive_widget_text ( ui, "false" ) ,
261
+ )
149
262
. clicked ( ) ;
150
263
clicked |= ui
151
- . re_radio_value ( self , Self :: IsNull , primitive_widget_text ( ui, "null" ) )
264
+ . re_radio_value (
265
+ & mut self . value ,
266
+ NullableBooleanValue :: IsNull ,
267
+ primitive_widget_text ( ui, "null" ) ,
268
+ )
152
269
. clicked ( ) ;
153
270
154
271
if clicked {
@@ -162,11 +279,3 @@ impl NullableBooleanFilter {
162
279
fn primitive_widget_text ( ui : & egui:: Ui , s : & str ) -> egui:: WidgetText {
163
280
SyntaxHighlightedBuilder :: primitive ( s) . into_widget_text ( ui. style ( ) )
164
281
}
165
-
166
- fn popup_header_ui ( ui : & mut egui:: Ui , column_name : & str ) {
167
- ui. label (
168
- SyntaxHighlightedBuilder :: body_default ( column_name)
169
- . with_keyword ( " is" )
170
- . into_widget_text ( ui. style ( ) ) ,
171
- ) ;
172
- }
0 commit comments