1
+ use std:: cell:: { Cell , RefCell } ;
2
+
1
3
#[ cfg( feature = "unstable-bolt-protocol-impl-v2" ) ]
2
4
use crate :: bolt:: { Discard , Summary , WrapExtra as _} ;
3
5
use crate :: {
@@ -26,6 +28,11 @@ impl Query {
26
28
}
27
29
}
28
30
31
+ pub fn with_params ( mut self , params : BoltMap ) -> Self {
32
+ self . params = params;
33
+ self
34
+ }
35
+
29
36
pub fn param < T : Into < BoltType > > ( mut self , key : & str , value : T ) -> Self {
30
37
self . params . put ( key. into ( ) , value. into ( ) ) ;
31
38
self
@@ -68,6 +75,14 @@ impl Query {
68
75
self . extra . value . contains_key ( key)
69
76
}
70
77
78
+ pub fn query ( & self ) -> & str {
79
+ & self . query
80
+ }
81
+
82
+ pub fn get_params ( & self ) -> & BoltMap {
83
+ & self . params
84
+ }
85
+
71
86
pub ( crate ) async fn run ( self , connection : & mut ManagedConnection ) -> Result < ( ) > {
72
87
let request = BoltRequest :: run ( & self . query , self . params , self . extra ) ;
73
88
Self :: try_run ( request, connection)
@@ -170,6 +185,15 @@ impl From<&str> for Query {
170
185
}
171
186
}
172
187
188
+ impl std:: fmt:: Debug for Query {
189
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
190
+ f. debug_struct ( "Query" )
191
+ . field ( "query" , & self . query )
192
+ . field ( "params" , & self . params )
193
+ . finish_non_exhaustive ( )
194
+ }
195
+ }
196
+
173
197
type QueryResult < T > = Result < T , backoff:: Error < Error > > ;
174
198
175
199
fn wrap_error < T > ( resp : impl IntoError , req : & ' static str ) -> QueryResult < T > {
@@ -217,6 +241,142 @@ fn unwrap_backoff(err: backoff::Error<Error>) -> Error {
217
241
}
218
242
}
219
243
244
+ #[ doc( hidden) ]
245
+ pub struct QueryParameter < ' x , T > {
246
+ value : Cell < Option < T > > ,
247
+ name : & ' static str ,
248
+ params : & ' x RefCell < BoltMap > ,
249
+ }
250
+
251
+ impl < ' x , T : Into < BoltType > > QueryParameter < ' x , T > {
252
+ #[ allow( dead_code) ]
253
+ pub fn new ( value : T , name : & ' static str , params : & ' x RefCell < BoltMap > ) -> Self {
254
+ Self {
255
+ value : Cell :: new ( Some ( value) ) ,
256
+ name,
257
+ params,
258
+ }
259
+ }
260
+ }
261
+
262
+ impl < T : Into < BoltType > > std:: fmt:: Display for QueryParameter < ' _ , T > {
263
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
264
+ let Some ( v) = self . value . replace ( None ) else {
265
+ return Err ( std:: fmt:: Error ) ;
266
+ } ;
267
+ self . params . borrow_mut ( ) . put ( self . name . into ( ) , v. into ( ) ) ;
268
+ write ! ( f, "${}" , self . name)
269
+ }
270
+ }
271
+
272
+ impl < T : Into < BoltType > > std:: fmt:: Debug for QueryParameter < ' _ , T > {
273
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
274
+ std:: fmt:: Display :: fmt ( self , f)
275
+ }
276
+ }
277
+
278
+ /// Create a query with a format! like syntax
279
+ ///
280
+ /// `query!` works similar to `format!`:
281
+ /// - The first argument is the query string with `{<name>}` placeholders
282
+ /// - Following that is a list of `name = value` parmeters arguments
283
+ /// - All placeholders in the query strings are replaced with query parameters
284
+ ///
285
+ /// The macro is a compiler-supported alternative to using the `params` method on `Query`.
286
+ ///
287
+ /// ## Differences from `format!` and limitations
288
+ ///
289
+ /// - Implicit `{name}` bindings without adding a `name = <value>` argument does not
290
+ /// actually create a new parameter; It does default string interpolation instead.
291
+ /// - Formatting parameters are largely ignored and have no effect on the query string.
292
+ /// - Argument values need to implement `Into<BoltType>` instead of `Display`
293
+ /// (and don't need to implement the latter)
294
+ /// - Only named placeholders syntax is supported (`{<name>}` instead of `{}`)
295
+ /// - This is because query parameters are always named
296
+ /// - By extension, adding an unnamed argument (e.g. `<value>` instead of `name = <value>`) is also not supported
297
+ ///
298
+ /// # Examples
299
+ ///
300
+ /// ```
301
+ /// use neo4rs::{query, Query};
302
+ ///
303
+ /// // This creates an unparametrized query.
304
+ /// let q: Query = query!("MATCH (n) RETURN n");
305
+ /// assert_eq!(q.query(), "MATCH (n) RETURN n");
306
+ /// assert!(q.get_params().is_empty());
307
+ ///
308
+ /// // This creates a parametrized query.
309
+ /// let q: Query = query!("MATCH (n) WHERE n.value = {answer} RETURN n", answer = 42);
310
+ /// assert_eq!(q.query(), "MATCH (n) WHERE n.value = $answer RETURN n");
311
+ /// assert_eq!(q.get_params().get::<i64>("answer").unwrap(), 42);
312
+ ///
313
+ /// // by contrast, using the implicit string interpolation syntax does not
314
+ /// // create a parameter, effectively being the same as `format!`.
315
+ /// let answer = 42;
316
+ /// let q: Query = query!("MATCH (n) WHERE n.value = {answer} RETURN n");
317
+ /// assert_eq!(q.query(), "MATCH (n) WHERE n.value = 42 RETURN n");
318
+ /// assert!(q.has_param_key("answer") == false);
319
+ ///
320
+ /// // The value can be any type that implements Into<BoltType>, it does not
321
+ /// // need to implement Display or Debug.
322
+ /// use neo4rs::{BoltInteger, BoltType};
323
+ ///
324
+ /// struct Answer;
325
+ /// impl Into<BoltType> for Answer {
326
+ /// fn into(self) -> BoltType {
327
+ /// BoltType::Integer(BoltInteger::new(42))
328
+ /// }
329
+ /// }
330
+ ///
331
+ /// let q: Query = query!("MATCH (n) WHERE n.value = {answer} RETURN n", answer = Answer);
332
+ /// assert_eq!(q.query(), "MATCH (n) WHERE n.value = $answer RETURN n");
333
+ /// assert_eq!(q.get_params().get::<i64>("answer").unwrap(), 42);
334
+ /// ```
335
+ #[ macro_export]
336
+ macro_rules! query {
337
+ // Create a unparametrized query
338
+ ( $query: expr) => {
339
+ $crate:: Query :: new( format!( $query) )
340
+ } ;
341
+
342
+ // Create a parametrized query with a format! like syntax
343
+ ( $query: expr $( , $( $input: tt) * ) ?) => {
344
+ $crate:: query!( @internal $query, [ ] $( ; $( $input) * ) ?)
345
+ } ;
346
+
347
+ ( @internal $query: expr, [ $( $acc: tt) * ] ; $name: ident = $value: expr $( , $( $rest: tt) * ) ?) => {
348
+ $crate:: query!( @internal $query, [ $( $acc) * ( $name = $value) ] $( ; $( $rest) * ) ?)
349
+ } ;
350
+
351
+ ( @internal $query: expr, [ $( $acc: tt) * ] ; $value: expr $( , $( $rest: tt) * ) ?) => {
352
+ compile_error!( "Only named parameter syntax (`name = value`) is supported" ) ;
353
+ } ;
354
+
355
+ ( @internal $query: expr, [ $( $acc: tt) * ] ; ) => {
356
+ $crate:: query!( @final $query; $( $acc) * )
357
+ } ;
358
+
359
+ ( @internal $query: expr, [ $( $acc: tt) * ] ) => {
360
+ $crate:: query!( @final $query; $( $acc) * )
361
+ } ;
362
+
363
+ ( @final $query: expr; $( ( $name: ident = $value: expr) ) * ) => { {
364
+ let params = $crate:: BoltMap :: default ( ) ;
365
+ let params = :: std:: cell:: RefCell :: new( params) ;
366
+
367
+ let query = format!( $query, $(
368
+ $name = $crate:: QueryParameter :: new(
369
+ $value,
370
+ stringify!( $name) ,
371
+ & params,
372
+ ) ,
373
+ ) * ) ;
374
+ let params = params. into_inner( ) ;
375
+
376
+ $crate:: Query :: new( query) . with_params( params)
377
+ } } ;
378
+ }
379
+
220
380
#[ cfg( test) ]
221
381
mod tests {
222
382
use super :: * ;
@@ -238,4 +398,27 @@ mod tests {
238
398
assert ! ( q. has_param_key( "name" ) ) ;
239
399
assert ! ( !q. has_param_key( "country" ) ) ;
240
400
}
401
+
402
+ #[ test]
403
+ fn query_macro ( ) {
404
+ let q = query ! (
405
+ "MATCH (n) WHERE n.name = {name} AND n.age > {age} RETURN n" ,
406
+ age = 42 ,
407
+ name = "Frobniscante" ,
408
+ ) ;
409
+
410
+ assert_eq ! (
411
+ q. query. as_str( ) ,
412
+ "MATCH (n) WHERE n.name = $name AND n.age > $age RETURN n"
413
+ ) ;
414
+
415
+ assert_eq ! (
416
+ q. params. get:: <String >( "name" ) . unwrap( ) ,
417
+ String :: from( "Frobniscante" )
418
+ ) ;
419
+ assert_eq ! ( q. params. get:: <i64 >( "age" ) . unwrap( ) , 42 ) ;
420
+
421
+ assert ! ( q. has_param_key( "name" ) ) ;
422
+ assert ! ( !q. has_param_key( "country" ) ) ;
423
+ }
241
424
}
0 commit comments