Paging in OpenLDAP, or "What, no LIMIT or OFFSET?"

Disclaimer: I’m not an LDAP expert, but I’ve done a whole mess of reading about OpenLDAP lately. Let the knowledgeable correct me where I err.

Paging in LDAP is somewhat of a pain, and by “somewhat” I mean “asymptotically approaching totally”. In the ldapsearch tool, for example, you have to use a “search extension” argument, as paging is not part of the search filter syntax. This is as opposed to SQL queries, where you may specify a LIMIT and OFFSET in the WHERE clause. Thus LDAP clients must implement the pagedResults search control (and the LDAP directory server must support it).

It gets worse. Check out the way the paging is implemented when following RFC 2696 (http://www.faqs.org/rfcs/rfc2696.html). You can only specify the size of the result set, not the offset or a page number. The LDAP server returns a cookie with the search results. The client uses the cookie in the next pagedResults query, and the server uses the cookie to figure out where to start the next set of results. LDAP clients must treat the cookie as opaque, i.e. they shouldn’t know how to do anything other than send the cookie back to the server.

Thus the only way to paginate results on the server side appears to be by looping through all results. The client must retain a cookie from each query for use in the next. Hrmmm. Can you guess who wrote RFC 2696?

At the time of this writing, there are two Ruby libraries for LDAP access, and ActiveLdap can use either as its adapter. To the extent that Net::LDAP supports the pagedResults control, it is only to prevent ActiveDirectory from choking when a query returns more than 1000 results. See ./lib/net/ldap.rb:1158 for the code that handles the pagedResult control.

Ruby/LDAP does support pagedResults, which I should have figured out from the line in the TODO file that started the discussion on the mailing list that started my research: “Add result pagination via LDAP::Controls”. So I think adding support for the control to the Ruby/LDAP adapter for ActiveLdap should be practical.

It might be possible to roll your own pagination, in a very ugly way, by calling the ActiveLdap::Base#search method with a block that throws away results before and after the desired page set. Net::LDAP yields each entry *after* adding it to the result_set array, so you would need to set the entry to nil and compact the result.

Alternatively, perhaps you could override the Net::LDAP search method to yield the entry to the block first, then add it to the result_set only if not nil.

It’s ugly every way you look.

Here’s the link that started my digging:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/195249

Paging in OpenLDAP, or "What, no LIMIT or OFFSET?"

Disclaimer: I’m not an LDAP expert, but I’ve done a whole mess of reading about OpenLDAP lately. Let the knowledgeable correct me where I err.

Paging in LDAP is somewhat of a pain, and by “somewhat” I mean “asymptotically approaching totally”. In the ldapsearch tool, for example, you have to use a “search extension” argument, as paging is not part of the search filter syntax. This is as opposed to SQL queries, where you may specify a LIMIT and OFFSET in the WHERE clause. Thus LDAP clients must implement the pagedResults search control (and the LDAP directory server must support it).

It gets worse. Check out the way the paging is implemented when following RFC 2696 (http://www.faqs.org/rfcs/rfc2696.html). You can only specify the size of the result set, not the offset or a page number. The LDAP server returns a cookie with the search results. The client uses the cookie in the next pagedResults query, and the server uses the cookie to figure out where to start the next set of results. LDAP clients must treat the cookie as opaque, i.e. they shouldn’t know how to do anything other than send the cookie back to the server.

Thus the only way to paginate results on the server side appears to be by looping through all results. The client must retain a cookie from each query for use in the next. Hrmmm. Can you guess who wrote RFC 2696?

At the time of this writing, there are two Ruby libraries for LDAP access, and ActiveLdap can use either as its adapter. To the extent that Net::LDAP supports the pagedResults control, it is only to prevent ActiveDirectory from choking when a query returns more than 1000 results. See ./lib/net/ldap.rb:1158 for the code that handles the pagedResult control.

Ruby/LDAP does support pagedResults, which I should have figured out from the line in the TODO file that started the discussion on the mailing list that started my research: “Add result pagination via LDAP::Controls”. So I think adding support for the control to the Ruby/LDAP adapter for ActiveLdap should be practical.

It might be possible to roll your own pagination, in a very ugly way, by calling the ActiveLdap::Base#search method with a block that throws away results before and after the desired page set. Net::LDAP yields each entry *after* adding it to the result_set array, so you would need to set the entry to nil and compact the result.

Alternatively, perhaps you could override the Net::LDAP search method to yield the entry to the block first, then add it to the result_set only if not nil.

It’s ugly every way you look.

Here’s the link that started my digging:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/195249

Lisp tutorials in Practical Common Lisp

They’re excellent. Peter Seibel’s book is available free online, as well as in print. I read enough of the free stuff to realize that I needed to stop and buy the book when I’m ready to do some projects in CL.

You can read it free here:
Common Lisp tutorial

launchd plist to run a reverse ssh tunnel

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version='1.0'>
<dict>
<key>Label</key><string>com.automatthew.ssh_tunnel</string>
<key>UserName</key><string>matthew</string>
<key>ProgramArguments</key>
<array>
        <string>/usr/bin/ssh</string>
        <string>-nNT</string>
        <string>-R 1389:127.0.0.1:389</string>
        <string>matthew@slice1.automatthew.com</string>
</array>
<key>Debug</key><false/>
<key>Disabled</key><false/>
<key>OnDemand</key><false/>
<key>RunAtLoad</key><false/>
</dict>
</plist>

launchd plist to run a reverse ssh tunnel

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version='1.0'>
<dict>
<key>Label</key><string>com.automatthew.ssh_tunnel</string>
<key>UserName</key><string>matthew</string>
<key>ProgramArguments</key>
<array>
        <string>/usr/bin/ssh</string>
        <string>-nNT</string>
        <string>-R 1389:127.0.0.1:389</string>
        <string>matthew@slice1.automatthew.com</string>
</array>
<key>Debug</key><false/>
<key>Disabled</key><false/>
<key>OnDemand</key><false/>
<key>RunAtLoad</key><false/>
</dict>
</plist>