Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid

Generating complex LDAP queries with Perl

by grinder (Bishop)
on Jul 17, 2009 at 10:46 UTC ( #781021=CUFP: print w/replies, xml ) Need Help??

For the longest time, I've managed to limit my LDAP queries to the absolute minimum, (foo=bar), or heaven forfend, (| (foo=bar) (foo=rat)) (and it was only upon rereading that I realised I had left off the last closing parenthesis, which just goes to show how easy it is to get this wrong!)

Yesterday I had to write some really tangled queries to tease out a thousand or so records from a directory at $work. I ran into grief trying to keep my conditionals (and parentheses) nested, and concluded that it would be much better to a have a program generate the queries for me. Hence, given something like:

my $now = time; my $filter = AND( "(objectClass=dynCand)", "(candActive=TRUE)", "(dynActive=TRUE)", OR( NOT( "(candBase=EXTERNAL)" ), AND( "(candBase=EXTERNAL)", NOT( OR( "(dynProfile=ante)", "(dynProfile=catp)", "(dynProfile=fci)", "(dynProfile=mgn)", "(dynProfile=oppse)", "(dynProfile=pic)", "(dynProfile=ren)", ) ) ) ), "(candDateStart<=$now)", "(candDateEnd>=$now)", "(dynDateStart<=$now)", "(dynDateEnd>=$now)", );

The bit in the middle says "I want all the records that are internal, or if they are external, all except some profiles".

when I run the above code, it produces the fabulous:

(& (& (& (& (& (& (& (objectClass=dynCand) (candActive=TRUE)) (dynActive=TRUE)) (| (!(candBase=EXTERNAL)) (& (candBase=EXTERNAL) (!(| (| (| (| (| (| (dynProfile=ante) (dynProfile=catp)) (dynProfile=fci)) (dynProfile=mgn)) (dynProfile=oppse)) (dynProfile=pic)) (dynProfile=ren)))))) (candDateStart<=1247827075)) (candDateEnd>=1247827075)) (dynDateStart<=1247827075)) (dynDateEnd>=1247827075))

The implementation is trivial:

sub AND { return _joiner( '&', @_ ); } sub OR { return _joiner( '|', @_ ); } sub _joiner { my $op = shift; my $filter = shift; while (my $cond = shift) { $filter = "($op $filter $cond)" if defined $cond; } return $filter; } sub NOT { return "(!$_[0])"; } sub IGNORE { return; }

I threw in the IGNORE function to remove parts of the construct. Since Perl doesn't have a multi-line comment block, and you cannot embed POD within a function call, this was the easiest way to comment out a subconditional. So, if you ever need to build hairy LDAP queries, this might be for you.

• another intruder with the mooring in the heart of the Perl

Replies are listed 'Best First'.
Re: Generating complex LDAP queries with Perl
by jwkrahn (Monsignor) on Jul 17, 2009 at 21:41 UTC
    while (my $cond = shift) { $filter = "($op $filter $cond)" if defined $cond; }

    The while conditional will exit the loop if $cond is false or undefined so the subsequent test for defined is redundant, $cond will not be undefined inside the loop.   Note that if $cond also contains "0" or "" it will exit the loop.

      Just to situate the historical context, I added the if defined $cond much later on in the piece, when I added the IGNORE() function to change an AND() or an OR() into a no-op.

      The idea was to ensure that "valid1", "valid2", undef, "valid3" produces a syntactically correct query, without a fourth half-baked conditional creeping in there.

      But you know what? You're absolutely correct. Given the above and the following:

      sub _joiner { my $op = shift; my $filter = shift; while (my $cond = shift) { $filter = "($op $filter $cond)"; } return $filter; } my $filter = AND( "(a=1)", IGNORE( "(b=1)", "(b=2)", ), "(c=1)", );

      It does indeed produce (& (a=1) (c=1)). Thank-you very much for this insight, I appreciate it.

      • another intruder with the mooring in the heart of the Perl

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://781021]
Approved by marto
Front-paged by moritz
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (2)
As of 2017-04-27 04:48 GMT
Find Nodes?
    Voting Booth?
    I'm a fool:

    Results (500 votes). Check out past polls.