|
15 | 15 | // specific language governing permissions and limitations |
16 | 16 | // under the License. |
17 | 17 |
|
18 | | -//! The session side of decoding a `PhysicalExpr` from protobuf. |
| 18 | +//! The session an expression decode runs under, and the `PhysicalExpr` |
| 19 | +//! face of the session-scoped [`ProtoDecoderRegistry`] that routes extension |
| 20 | +//! expressions to their decoders by name — [`register_physical_expr`], |
| 21 | +//! [`decode_physical_expr`] and [`physical_expr_names`], all keyed on |
| 22 | +//! `dyn PhysicalExpr`. The store itself is shared with every other extension |
| 23 | +//! kind, so a session carries one registry. |
19 | 24 | //! |
20 | 25 | //! The decode context in `datafusion-physical-expr-common` is generic over its |
21 | 26 | //! session type: a decoder may need the session, and what it needs — |
|
25 | 30 | //! expressions decode under it. `datafusion-proto` builds one from the |
26 | 31 | //! `TaskContext` it decodes under. |
27 | 32 |
|
| 33 | +use std::sync::Arc; |
| 34 | + |
| 35 | +use datafusion_common::Result; |
28 | 36 | use datafusion_common::config::ConfigOptions; |
29 | 37 | use datafusion_expr::registry::FunctionRegistry; |
| 38 | +use datafusion_physical_expr_common::physical_expr::PhysicalExpr; |
30 | 39 | pub use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx; |
| 40 | +pub use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx; |
| 41 | +pub use datafusion_proto_models::ProtoDecoderRegistry; |
| 42 | +use datafusion_proto_models::protobuf::PhysicalExprNode; |
| 43 | +use datafusion_proto_models::protobuf::physical_expr_node::ExprType; |
31 | 44 |
|
32 | 45 | /// What an expression decoder can see of the session it runs under: the |
33 | 46 | /// function registry, for resolving UDFs by name, and the configuration |
@@ -68,3 +81,206 @@ impl std::fmt::Debug for ExprDecodeSession<'_> { |
68 | 81 | f.debug_struct("ExprDecodeSession").finish_non_exhaustive() |
69 | 82 | } |
70 | 83 | } |
| 84 | +/// The wire name of an extension [`PhysicalExpr`], and the constructor that |
| 85 | +/// rebuilds it. |
| 86 | +/// |
| 87 | +/// Only extension expressions implement this. A built-in expression has an |
| 88 | +/// `ExprType` variant of its own, keeps the inherent `try_from_proto` |
| 89 | +/// dispatched from that variant, and has no wire name to be registered under — |
| 90 | +/// so [`register_physical_expr`] will not accept one. |
| 91 | +/// |
| 92 | +/// One impl block says what the expression is called and how it decodes; its |
| 93 | +/// `try_to_proto` on [`PhysicalExpr`] writes the matching node with |
| 94 | +/// [`extension_expr_node`]: |
| 95 | +/// |
| 96 | +/// ```ignore |
| 97 | +/// impl PhysicalExpr for MyExpr { |
| 98 | +/// // ... |
| 99 | +/// fn try_to_proto( |
| 100 | +/// &self, |
| 101 | +/// ctx: &PhysicalExprEncodeCtx<'_>, |
| 102 | +/// ) -> Result<Option<PhysicalExprNode>> { |
| 103 | +/// // Stamps `Self::NAME`, so the encoded name and the registry key |
| 104 | +/// // cannot drift apart. |
| 105 | +/// Ok(Some(extension_expr_node::<Self>( |
| 106 | +/// ctx, |
| 107 | +/// self.state.to_bytes()?, |
| 108 | +/// self.children(), |
| 109 | +/// )?)) |
| 110 | +/// } |
| 111 | +/// } |
| 112 | +/// |
| 113 | +/// impl ExtensionExprFromProto for MyExpr { |
| 114 | +/// // Namespace the name with the owning crate, so a collision with |
| 115 | +/// // another crate is an error at registration and not a wrong decode. |
| 116 | +/// const NAME: &'static str = "my_crate.MyExpr"; |
| 117 | +/// |
| 118 | +/// fn try_from_proto( |
| 119 | +/// node: &PhysicalExprNode, |
| 120 | +/// ctx: &PhysicalExprDecodeCtx<'_, ExprDecodeSession<'_>>, |
| 121 | +/// ) -> Result<Arc<dyn PhysicalExpr>> { |
| 122 | +/// let extension = expect_expr_variant!( |
| 123 | +/// node, |
| 124 | +/// physical_expr_node::ExprType::Extension, |
| 125 | +/// "Extension", |
| 126 | +/// ); |
| 127 | +/// let state = MyState::from_bytes(&extension.expr)?; |
| 128 | +/// let children = ctx.decode_children_expressions(&extension.inputs)?; |
| 129 | +/// Ok(Arc::new(MyExpr::new(state, children))) |
| 130 | +/// } |
| 131 | +/// } |
| 132 | +/// |
| 133 | +/// let mut registry = ProtoDecoderRegistry::new(); |
| 134 | +/// register_physical_expr::<MyExpr>(&mut registry)?; |
| 135 | +/// let config = SessionConfig::new().with_extension(Arc::new(registry)); |
| 136 | +/// ``` |
| 137 | +pub trait ExtensionExprFromProto: PhysicalExpr + Sized { |
| 138 | + /// The name this expression type is written and registered under. |
| 139 | + /// |
| 140 | + /// Namespace it with the owning crate — `"my_crate.MyExpr"`, not |
| 141 | + /// `"MyExpr"` — so that two independent crates registering into the same |
| 142 | + /// session collide at registration time instead of silently decoding each |
| 143 | + /// other's nodes. Never write it by hand on the wire: |
| 144 | + /// [`extension_expr_node`] stamps it for you. |
| 145 | + const NAME: &'static str; |
| 146 | + |
| 147 | + /// Rebuild the expression from the `PhysicalExprNode` its |
| 148 | + /// [`PhysicalExpr::try_to_proto`] wrote. |
| 149 | + /// |
| 150 | + /// Takes the whole node — the exact inverse of what `try_to_proto` |
| 151 | + /// returns — so the constructor can also see outer-node fields such as |
| 152 | + /// `expr_id`. `ctx.session()` gives the function registry and the |
| 153 | + /// configuration options. |
| 154 | + fn try_from_proto( |
| 155 | + node: &PhysicalExprNode, |
| 156 | + ctx: &PhysicalExprDecodeCtx<'_, ExprDecodeSession<'_>>, |
| 157 | + ) -> Result<Arc<dyn PhysicalExpr>>; |
| 158 | +} |
| 159 | + |
| 160 | +/// Build the `PhysicalExprNode` for an extension expression `T`: an |
| 161 | +/// `Extension` variant carrying `payload`, the encoded `children`, and `T`'s |
| 162 | +/// [`NAME`](ExtensionExprFromProto::NAME). |
| 163 | +/// |
| 164 | +/// The one supported way for an extension expression to write itself. It is a |
| 165 | +/// free function rather than a method on [`PhysicalExprEncodeCtx`] because the |
| 166 | +/// context lives in `datafusion-physical-expr-common`, below the crate that |
| 167 | +/// can name [`ExtensionExprFromProto`] — the same layering that makes the |
| 168 | +/// decode context generic over its session. |
| 169 | +pub fn extension_expr_node<T: ExtensionExprFromProto>( |
| 170 | + ctx: &PhysicalExprEncodeCtx<'_>, |
| 171 | + payload: Vec<u8>, |
| 172 | + children: Vec<&Arc<dyn PhysicalExpr>>, |
| 173 | +) -> Result<PhysicalExprNode> { |
| 174 | + Ok(PhysicalExprNode { |
| 175 | + expr_type: Some(ExprType::Extension( |
| 176 | + datafusion_proto_models::protobuf::PhysicalExtensionExprNode { |
| 177 | + expr: payload, |
| 178 | + inputs: ctx.encode_children_expressions(children)?, |
| 179 | + expr_name: Some(T::NAME.to_string()), |
| 180 | + }, |
| 181 | + )), |
| 182 | + ..Default::default() |
| 183 | + }) |
| 184 | +} |
| 185 | + |
| 186 | +/// How this facade stores a decoder in the shared registry: a function pointer |
| 187 | +/// to the monomorphized [`ExtensionExprFromProto::try_from_proto`]. |
| 188 | +/// |
| 189 | +/// Deliberately private, and the same type on both the |
| 190 | +/// [`register_physical_expr`] and the [`decode_physical_expr`] side. |
| 191 | +/// [`ExtensionExprFromProto`] is the public contract and |
| 192 | +/// [`decode_physical_expr`] is the public way to invoke one, so this can become |
| 193 | +/// something else — a `dyn` decoder object, to admit stateful or closure |
| 194 | +/// decoders, which is what an FFI decoder needs — without a breaking change. |
| 195 | +type PhysicalExprDecoder = fn( |
| 196 | + &PhysicalExprNode, |
| 197 | + &PhysicalExprDecodeCtx<'_, ExprDecodeSession<'_>>, |
| 198 | +) -> Result<Arc<dyn PhysicalExpr>>; |
| 199 | + |
| 200 | +/// Register `T` in `registry` under its [`ExtensionExprFromProto::NAME`]. |
| 201 | +/// |
| 202 | +/// Registering the same type twice is a no-op. Registering a *different* type |
| 203 | +/// under a name already taken is an error, so collisions surface here rather |
| 204 | +/// than as a wrong decode later. The name is scoped to `dyn PhysicalExpr`, so a |
| 205 | +/// plan or a data source may use the same name in the same registry. |
| 206 | +/// |
| 207 | +/// # Who builds the registry |
| 208 | +/// |
| 209 | +/// The application that owns the session builds it. A session carries at most |
| 210 | +/// one registry, and `SessionConfig::with_extension` replaces what is there, so |
| 211 | +/// a library must never attach a registry of its own: it would silently discard |
| 212 | +/// another library's. A library exposes a function that fills a registry it is |
| 213 | +/// handed, and the application composes them. One registry holds every kind, so |
| 214 | +/// a library registers its plans and its expressions into the same object. |
| 215 | +/// |
| 216 | +/// # A built-in expression cannot be registered |
| 217 | +/// |
| 218 | +/// The bound is [`ExtensionExprFromProto`], which only extension expressions |
| 219 | +/// implement. A built-in keeps an inherent `try_from_proto`, dispatched from |
| 220 | +/// its own `ExprType` variant: |
| 221 | +/// |
| 222 | +/// ``` |
| 223 | +/// use datafusion_physical_expr::expressions::Column; |
| 224 | +/// |
| 225 | +/// let _ = Column::try_from_proto; |
| 226 | +/// ``` |
| 227 | +/// |
| 228 | +/// but it is not an extension, and registering it does not compile: |
| 229 | +/// |
| 230 | +/// ```compile_fail |
| 231 | +/// use datafusion_physical_expr::expressions::Column; |
| 232 | +/// use datafusion_physical_expr::proto::register_physical_expr; |
| 233 | +/// use datafusion_proto_models::ProtoDecoderRegistry; |
| 234 | +/// |
| 235 | +/// let mut registry = ProtoDecoderRegistry::new(); |
| 236 | +/// register_physical_expr::<Column>(&mut registry).unwrap(); |
| 237 | +/// ``` |
| 238 | +pub fn register_physical_expr<T: ExtensionExprFromProto>( |
| 239 | + registry: &mut ProtoDecoderRegistry, |
| 240 | +) -> Result<()> { |
| 241 | + registry.register_decoder::<dyn PhysicalExpr, T, PhysicalExprDecoder>( |
| 242 | + T::NAME, |
| 243 | + T::try_from_proto, |
| 244 | + ) |
| 245 | +} |
| 246 | + |
| 247 | +/// Decode `node` with the extension expression decoder registered under the |
| 248 | +/// name `node` carries. |
| 249 | +/// |
| 250 | +/// The name is read from the node's `Extension` variant rather than passed in, |
| 251 | +/// so a caller cannot pair a node with a name it does not carry. |
| 252 | +/// |
| 253 | +/// `None` means "this node names no extension expression decoder of ours": it |
| 254 | +/// is not an extension node, it carries no name, or no registered name matches. |
| 255 | +/// The caller then falls back to the `PhysicalExtensionCodec` chain. |
| 256 | +/// `Some(Err(..))` means the decoder that *does* own the name failed, which is |
| 257 | +/// fatal: falling back there would let a codec decode the payload wrongly, the |
| 258 | +/// very thing the name exists to prevent. |
| 259 | +pub fn decode_physical_expr( |
| 260 | + registry: &ProtoDecoderRegistry, |
| 261 | + node: &PhysicalExprNode, |
| 262 | + ctx: &PhysicalExprDecodeCtx<'_, ExprDecodeSession<'_>>, |
| 263 | +) -> Option<Result<Arc<dyn PhysicalExpr>>> { |
| 264 | + let name = expr_name(node)?; |
| 265 | + let decoder = registry.decoder::<dyn PhysicalExpr, PhysicalExprDecoder>(name)?; |
| 266 | + Some(decoder(node, ctx)) |
| 267 | +} |
| 268 | + |
| 269 | +/// Every extension expression name registered in `registry`, in arbitrary |
| 270 | +/// order. |
| 271 | +/// |
| 272 | +/// Names registered for another kind are not included. |
| 273 | +pub fn physical_expr_names( |
| 274 | + registry: &ProtoDecoderRegistry, |
| 275 | +) -> impl Iterator<Item = &str> { |
| 276 | + registry.names::<dyn PhysicalExpr>() |
| 277 | +} |
| 278 | + |
| 279 | +/// The registry name `node` was written with, if it is an extension node that |
| 280 | +/// carries one. |
| 281 | +fn expr_name(node: &PhysicalExprNode) -> Option<&str> { |
| 282 | + match node.expr_type.as_ref()? { |
| 283 | + ExprType::Extension(extension) => extension.expr_name.as_deref(), |
| 284 | + _ => None, |
| 285 | + } |
| 286 | +} |
0 commit comments