Using MongoDB, Solr NoSQL Data Stores

Non-relational data stores are commonly used for applications that don’t need either full relational algebra or must scale.

The MongoDB example code is in the file src/loving_snippets/mongo_news.lisp. The Solr example code is in the subdirectories src/solr_examples.

Note for the fifth edition: The Common Lisp cl-mongo library is now unsupported for versions of MongoDB later than 2.6 (released in 2016). You can install an old version of MongoDB for macOS or for Linux. I have left the MongoDB examples in this section but I can’t recommend that you use cl-mongo and MongoDB for any serious applications.

Brewer’s CAP theorem states that a distributed data storage system comprised of multiple nodes can be robust to two of three of the following guarantees: all nodes always have a Consistent view of the state of data, general Availablity of data if not all nodes are functioning, and Partition tolerance so clients can still communicate with the data storage system when parts of the system are unavailable because of network failures. The basic idea is that different applications have different requirements and sometimes it makes sense to reduce system cost or improve scalability by easing back on one of these requirements.

A good example is that some applications may not need transactions (the first guarantee) because it is not important if clients sometimes get data that is a few seconds out of date.

MongoDB allows you to choose consistency vs. availability vs. efficiency.

I cover the Solr indexing and search service (based on Lucene) both because a Solr indexed document store is a type of NoSQL data store and also because I believe that you will find Solr very useful for building systems, if you don’t already use it.

MongoDB

The following discussion of MongoDB is based on just my personal experience, so I am not covering all use cases. I have used MongoDB for:

  • Small clusters of MongoDB nodes to analyze social media data, mostly text mining and sentiment analysis. In all cases for each application I ran MongoDB with one write master (i.e., I wrote data to this one node but did not use it for reads) and multiple read-only slave nodes. Each slave node would run on the same server that was usually performing a single bit of analytics.
  • Multiple very large independent clusters for web advertising. Problems faced included trying to have some level of consistency across data centers. Replica sets were used within each data center.
  • Running a single node MongoDB instance for low volume data collection and analytics.

One of the advantages of MongoDB is that it is very “developer friendly” because it supports ad-hoc document schemas and interactive queries. I mentioned that MongoDB allows you to choose consistency vs. availability vs. efficiency. When you perform MongoDB writes you can specify some granularity of what constitutes a “successful write” by requiring that a write is performed at a specific number of nodes before the client gets acknowledgement that the write was successful. This requirement adds overhead to each write operation and can cause writes to fail if some nodes are not available.

The MongoDB online documentation is very good. You don’t have to read it in order to have fun playing with the following Common Lisp and MongoDB examples, but if you find that MongoDB is a good fit for your needs after playing with these examples then you should read the documentation. I usually install MongoDB myself but it is sometimes convenient to use a hosting service. There are several well regarded services and I have used MongoHQ.

At this time there is no official Common Lisp support for accessing MongoDB but there is a useful project by Alfons Haffmans’ cl-mongo that will allow us to write Common Lisp client applications and have access to most of the capabilities of MongoDB.

The file src/mongo_news.lisp contains the example code used in the next three sessions.

Adding Documents

The following repl listing shows the cl-mongo APIs for creating a new document, adding elements (attributes) to it, and inserting it in a MongoDB data store:

 1 (ql:quickload "cl-mongo")
 2 
 3 (cl-mongo:db.use "news")
 4 
 5 (defun add-article (uri title text)
 6   (let ((doc (cl-mongo:make-document)))
 7     (cl-mongo:add-element "uri" uri doc)
 8     (cl-mongo:add-element "title" title doc)
 9     (cl-mongo:add-element "text" text doc)
10     (cl-mongo:db.insert "article" doc)))
11 
12 ;; add a test document:
13 (add-article "http://test.com" "article title 1" "article text 1")

In this example, three string attributes were added to a new document before it was saved.

Fetching Documents by Attribute

We will start by fetchng and pretty-printing all documents in the collection articles and fetching all articles a list of nested lists where the inner nested lists are document URI, title, and text:

 1 (defun print-articles ()
 2   (cl-mongo:pp (cl-mongo:iter (cl-mongo:db.find "article" :all))))
 3 
 4 ;; for each document, use the cl-mongo:get-element on
 5 ;; each element we want to save:
 6 (defun article-results->lisp-data (mdata)
 7   (let ((ret '()))
 8     ;;(print (list "size of result=" (length mdata)))
 9     (dolist (a mdata)
10       ;;(print a)
11       (push
12         (list
13          (cl-mongo:get-element "uri" a)
14          (cl-mongo:get-element "title" a)
15          (cl-mongo:get-element "text" a))
16         ret)))
17     ret))
18 
19 (defun get-articles ()
20   (article-results->lisp-data
21     (cadr (cl-mongo:db.find "article" :all))))

Output for these two functions looks like:

 1 * (print-articles)
 2 
 3 {
 4   "_id" -> objectid(99778A792EBB4F76B82F75C6)
 5   "uri"  ->  http://test.com/3
 6   "title"  ->  article title 3
 7   "text"  ->  article text 3
 8 }
 9 
10 {
11   "_id" -> objectid(D47DEF3CFDB44DEA92FD9E56)
12   "uri"  ->  http://test.com/2
13   "title"  ->  article title 2
14   "text"  ->  article text 2
15 }
16 
17 * (get-articles)
18 
19 (("http://test.com/2" "article title 2" "article text 2")
20  ("http://test.com/3" "article title 3" "article text 3"))

By reusing the function article-results->lisp-data defined in the last section, we can also search for JSON documents using regular expressions matching attribute values:

 1 ;; find documents where substring 'str' is in the title:
 2 (defun search-articles-title (str)
 3   (article-results->lisp-data
 4     (cadr
 5       (cl-mongo:iter
 6        (cl-mongo:db.find
 7          "article"
 8          (cl-mongo:kv
 9             "title"     // TITLE ATTRIBUTE
10             (cl-mongo:kv "$regex" str)) :limit 10)))))
11 
12 ;; find documents where substring 'str' is in the text element:
13 (defun search-articles-text (str) 
14   (article-results->lisp-data
15     (cadr
16       (cl-mongo:db.find
17         "article"
18         (cl-mongo:kv
19            "text"        // TEXT ATTRIBUTE
20            (cl-mongo:kv "$regex" str)) :limit 10))))

I set the limit to return a maximum of ten documents. If you do not set the limit, this example code only returns one search result. The following repl listing shows the results from calling function search-articles-text:

1 * (SEARCH-ARTICLES-TEXT "text")
2 
3 (("http://test.com/2" "article title 2" "article text 2")
4  ("http://test.com/3" "article title 3" "article text 3"))
5 * (SEARCH-ARTICLES-TEXT "3")
6 
7 (("http://test.com/3" "article title 3" "article text 3"))

I find using MongoDB to be especially effective when experimenting with data and code. The schema free JSON document format, using interactive queries using the mongo shell, and easy to use client libraries like clouchdb for Common Lisp will let you experiment with a lot of ideas in a short period of time. The following listing shows the use of the interactive mongo shell. The database news is the database used in the MongoDB examples in this chapter; you will notice that I also have other databases for other projects on my laptop:

 1 ->  src git:(master) mongo
 2 MongoDB shell version: 2.4.5
 3 connecting to: test
 4 > show dbs
 5 kbsportal   0.03125GB
 6 knowledgespace  0.03125GB
 7 local   (empty)
 8 mark_twitter    0.0625GB
 9 myfocus 0.03125GB
10 news    0.03125GB
11 nyt 0.125GB
12 twitter 0.125GB
13 > use news
14 switched to db news
15 > show collections
16 article
17 system.indexes
18 > db.article.find()
19 { "uri" : "http://test.com/3",
20  "title" : "article title 3",
21  "text" : "article text 3",
22  "_id" : ObjectId("99778a792ebb4f76b82f75c6") }
23 { "uri" : "http://test.com/2",
24  "title" : "article title 2",
25  "text" : "article text 2",
26  "_id" : ObjectId("d47def3cfdb44dea92fd9e56") }
27 > 

Line 1 of this listing shows starting the mongo shell. Line 4 shows how to list all databases in the data store. In line 13 I select the database “news” to use. Line 15 prints out the names of all collections in the current database “news”. Line 18 prints out all documents in the “articles” collection. You can read the documentation for the mongo shell for more options like selective queries, adding indices, etc.

When you run a MongoDB service on your laptop, also try the admin interface on http://localhost:28017/.

A Common Lisp Solr Client

The Lucene project is one of the most widely used Apache Foundation projects. Lucene is a flexible library for preprocessing and indexing text, and searching text. I have personally used Lucene on so many projects that it would be difficult to count them. The Apache Solr Project adds a network interface to the Lucene text indexer and search engine. Solr also adds other utility features to Lucene:

  • While Lucene is a library to embed in your programs, Solr is a complete system.
  • Solr provides good defaults for preprocessing and indexing text and also provides rich support for managing structured data.
  • Provides both XML and JSON APIs using HTTP and REST.
  • Supports faceted search, geospatial search, and provides utilities for highlighting search terms in surrounding text of search results.
  • If your system ever grows to a very large number of users, Solr supports scaling via replication.

I hope that you will find the Common Lisp example Solr client code in the following sections helps you make Solr part of large systems that you write using Common Lisp.

Installing Solr

Download a binary Solr distribution and un-tar or un-zip this Solr distribution, cd to the distribution directory, then cd to the example directory and run:

1  ~/solr/example>  java -jar start.jar

You can access the Solr Admin Web App at http://localhost:8983/solr/#/. This web app can be seen in the following screen shot:

Solr Admin Web App

There is no data in the Solr example index yet, so following the Solr tutorial instructions:

 1  ~/> cd ~/solr/example/exampledocs
 2  ~/solr/example/exampledocs> java -jar post.jar *.xml
 3 SimplePostTool version 1.5
 4 Posting files to base url http://localhost:8983/solr/update
 5         using content-type application/xml..
 6 POSTing file gb18030-example.xml
 7 POSTing file hd.xml
 8 POSTing file ipod_other.xml
 9 POSTing file ipod_video.xml
10 POSTing file manufacturers.xml
11 POSTing file mem.xml
12 POSTing file money.xml
13 POSTing file monitor.xml
14 POSTing file monitor2.xml
15 POSTing file mp500.xml
16 POSTing file sd500.xml
17 POSTing file solr.xml
18 POSTing file utf8-example.xml
19 POSTing file vidcard.xml
20 14 files indexed.
21 COMMITting Solr index changes
22            to http://localhost:8983/solr/update..
23 Time spent: 0:00:00.480

You will learn how to add documents to Solr directly in your Common Lisp programs in a later section.

Assuming that you have a fast Internet connection so that downloading Solr was quick, you have hopefully spent less than five or six minutes getting Solr installed and running with enough example search data for the Common Lisp client examples we will play with. Solr is a great tool for storing, indexing, and searching data. I recommend that you put off reading the official Solr documentation for now and instead work through the Common Lisp examples in the next two sections. Later, if you want to use Solr then you will need to carefully read the Solr documentation.

Solr’s REST Interface

The Solr REST Interface Documentation documents how to perform search using HTTP GET requests. All we need to do is implement this in Common Lisp which you will see is easy.

Assuming that you have Solr running and the example data loaded, we can try searching for documents with, for example, the word “British” using the URL http://localhost:8983/solr/select?q=British. This is a REST request URL and you can use utilities like curl or wget to fetch the XML data. I fetched the data in a web browser, as seen in the following screen shot of a Firefox web browser (I like the way Firefox formats and displays XML data):

Solr Search Results as XML Data

The attributes in the returned search results need some explanation. We indexed several example XML data files, one of which contained the following XML element that we just saw as a search result:

 1 <doc>
 2   <field name="id">GBP</field>
 3   <field name="name">One British Pound</field>
 4   <field name="manu">U.K.</field>
 5   <field name="manu_id_s">uk</field>
 6   <field name="cat">currency</field>
 7   <field name="features">Coins and notes</field>
 8   <field name="price_c">1,GBP</field>
 9   <field name="inStock">true</field>
10 </doc>

So, the search result has the same attributes as the structured XML data that was added to the Solr search index. Solr’s capability for indexing structured data is a superset of just indexing plain text. If for example we were indexing news stories, then example input data might look like:

1 <doc>
2   <field name="id">new_story_0001</field>
3   <field name="title">Fishing Season Opens</field>
4   <field name="text">Fishing season opens on Friday in Oak Creek.</field>
5 </doc>

With this example, a search result that returned this document as a result would return attributes id, title, and text, and the values of these three attributes.

By default the Solr web service returns XML data as seen in the last screen shot. For our examples, I prefer using JSON so we are going to always add a request parameter wt=json to all REST calls. The following screen shot shows the same data returned in JSON serialization format instead of XML format of a Chrome web browser (I like the way Chrome formats and displays JSON data with the JSONView Chrome Browser extension):

Solr Search Results as JSON Data

You can read the full JSON REST Solr documentation later, but for our use here we will use the following search patterns:

  • http://localhost:8983/solr/select?q=British+One&wt=json - search for documents with either of the words “British” or “one” in them. Note that in URIs that the “+” character is used to encode a space character. If you wanted a “+” character you would encode it with “%2B” and a space character is encoded as “%20”. The default Solr search option is an OR of the search terms, unlike, for example, Google Search.
  • http://localhost:8983/solr/select?q=British+AND+one&wt=json - search for documents that contain both of the words “British” and “one” in them. The search term in plain text is “British AND one”.

As we sawearlier in Network Programming it is fairly simple to use the drakma and cl-json Common Lisp libraries to call REST services that return JSON data. The function do-search defined in the next listing (all the Solr example code is in the file src/solr-client.lisp) constructs a query URI as we saw in the last section and uses the Drackma library to perform an HTTP GET operation and the cl-json library to parse the returned string containing JSON data into Lisp data structures:

 1 (ql:quickload :drakma)
 2 (ql:quickload :cl-json)
 3 
 4 (defun do-search (&rest terms)
 5   (let ((query-string (format nil "~{~A~^+AND+~}" terms)))
 6    (cl-json:decode-json-from-string
 7      (drakma:http-request
 8        (concatenate 
 9         'string
10         "http://localhost:8983/solr/select?q="
11         query-string
12         "&wt=json")))))

This example code does return the search results as Lisp list data; for example:

 1 * (do-search "British" "one")
 2 
 3 ((:RESPONSE-HEADER (:STATUS . 0) (:*Q-TIME . 1)
 4   (:PARAMS (:Q . "British+AND+one") (:WT . "json")))
 5  (:RESPONSE (:NUM-FOUND . 6) (:START . 0)
 6   (:DOCS
 7    ((:ID . "GBP") (:NAME . "One British Pound") (:MANU . "U.K.")
 8     (:MANU--ID--S . "uk") (:CAT "currency")
 9     (:FEATURES "Coins and notes")
10     (:PRICE--C . "1,GBP") (:IN-STOCK . T)
11     (:--VERSION-- . 1440194917628379136))
12    ((:ID . "USD") (:NAME . "One Dollar")
13     (:MANU . "Bank of America")
14     (:MANU--ID--S . "boa") (:CAT "currency")
15     (:FEATURES "Coins and notes")
16     (:PRICE--C . "1,USD") (:IN-STOCK . T)
17     (:--VERSION-- . 1440194917624184832))
18    ((:ID . "EUR") (:NAME . "One Euro")
19     (:MANU . "European Union")
20     (:MANU--ID--S . "eu") (:CAT "currency")
21     (:FEATURES "Coins and notes")
22     (:PRICE--C . "1,EUR") (:IN-STOCK . T)
23     (:--VERSION-- . 1440194917626281984))
24    ((:ID . "NOK") (:NAME . "One Krone")
25     (:MANU . "Bank of Norway")
26     (:MANU--ID--S . "nor") (:CAT "currency")
27     (:FEATURES "Coins and notes")
28     (:PRICE--C . "1,NOK") (:IN-STOCK . T)
29     (:--VERSION-- . 1440194917631524864))
30    ((:ID . "0579B002")
31     (:NAME . "Canon PIXMA MP500 All-In-One Photo Printer")
32     (:MANU . "Canon Inc.")
33     (:MANU--ID--S . "canon")
34     (:CAT "electronics" "multifunction printer"
35      "printer" "scanner" "copier")
36     (:FEATURES "Multifunction ink-jet color photo printer"
37      "Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi"
38      "2.5\" color LCD preview screen" "Duplex Copying"
39      "Printing speed up to 29ppm black, 19ppm color" "Hi-Speed USB"
40      "memory card: CompactFlash, Micro Drive, SmartMedia,
41       Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard")
42     (:WEIGHT . 352.0) (:PRICE . 179.99)
43     (:PRICE--C . "179.99,USD")
44     (:POPULARITY . 6) (:IN-STOCK . T)
45     (:STORE . "45.19214,-93.89941")
46     (:--VERSION-- . 1440194917651447808))
47    ((:ID . "SOLR1000")
48     (:NAME . "Solr, the Enterprise Search Server")
49     (:MANU . "Apache Software Foundation")
50     (:CAT "software" "search")
51     (:FEATURES "Advanced Full-Text Search Capabilities using Lucene"
52      "Optimized for High Volume Web Traffic"
53      "Standards Based Open Interfaces - XML and HTTP"
54      "Comprehensive HTML Administration Interfaces"
55      "Scalability - Efficient Replication to other Solr Search Servers"
56      "Flexible and Adaptable with XML configuration and Schema"
57      "Good unicode support: héllo (hello with an accent over the e)")
58     (:PRICE . 0.0) (:PRICE--C . "0,USD") (:POPULARITY . 10) (:IN-STOCK . T)
59     (:INCUBATIONDATE--DT . "2006-01-17T00:00:00Z")
60     (:--VERSION-- . 1440194917671370752)))))

I might modify the search function to return just the fetched documents as a list, discarding the returned Solr meta data:

 1 * (cdr (cadddr (cadr (do-search "British" "one"))))
 2 
 3 (((:ID . "GBP") (:NAME . "One British Pound") (:MANU . "U.K.")
 4   (:MANU--ID--S . "uk") (:CAT "currency") (:FEATURES "Coins and notes")
 5   (:PRICE--C . "1,GBP") (:IN-STOCK . T)
 6   (:--VERSION-- . 1440194917628379136))
 7  ((:ID . "USD") (:NAME . "One Dollar") (:MANU . "Bank of America")
 8   (:MANU--ID--S . "boa") (:CAT "currency") (:FEATURES "Coins and notes")
 9   (:PRICE--C . "1,USD") (:IN-STOCK . T)
10   (:--VERSION-- . 1440194917624184832))
11  ((:ID . "EUR") (:NAME . "One Euro") (:MANU . "European Union")
12   (:MANU--ID--S . "eu") (:CAT "currency") (:FEATURES "Coins and notes")
13   (:PRICE--C . "1,EUR") (:IN-STOCK . T)
14   (:--VERSION-- . 1440194917626281984))
15  ((:ID . "NOK") (:NAME . "One Krone") (:MANU . "Bank of Norway")
16   (:MANU--ID--S . "nor") (:CAT "currency")
17   (:FEATURES "Coins and notes")
18   (:PRICE--C . "1,NOK") (:IN-STOCK . T)
19   (:--VERSION-- . 1440194917631524864))
20  ((:ID . "0579B002")
21   (:NAME . "Canon PIXMA MP500 All-In-One Photo Printer")
22   (:MANU . "Canon Inc.") (:MANU--ID--S . "canon")
23   (:CAT "electronics" "multifunction printer" "printer"
24    "scanner" "copier")
25   (:FEATURES "Multifunction ink-jet color photo printer"
26    "Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi"
27    "2.5\" color LCD preview screen" "Duplex Copying"
28    "Printing speed up to 29ppm black, 19ppm color" "Hi-Speed USB"
29    "memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick,
30     Memory Stick Pro, SD Card, and MultiMediaCard")
31   (:WEIGHT . 352.0) (:PRICE . 179.99) (:PRICE--C . "179.99,USD")
32   (:POPULARITY . 6) (:IN-STOCK . T) (:STORE . "45.19214,-93.89941")
33   (:--VERSION-- . 1440194917651447808))
34  ((:ID . "SOLR1000") (:NAME . "Solr, the Enterprise Search Server")
35   (:MANU . "Apache Software Foundation") (:CAT "software" "search")
36   (:FEATURES "Advanced Full-Text Search Capabilities using Lucene"
37    "Optimized for High Volume Web Traffic"
38    "Standards Based Open Interfaces - XML and HTTP"
39    "Comprehensive HTML Administration Interfaces"
40    "Scalability - Efficient Replication to other Solr Search Servers"
41    "Flexible and Adaptable with XML configuration and Schema"
42    "Good unicode support: héllo (hello with an accent over the e)")
43   (:PRICE . 0.0) (:PRICE--C . "0,USD") (:POPULARITY . 10) (:IN-STOCK . T)
44   (:INCUBATIONDATE--DT . "2006-01-17T00:00:00Z")
45   (:--VERSION-- . 1440194917671370752)))

There are a few more important details if you want to add Solr search to your Common Lisp applications. When there are many search results you might want to fetch a limited number of results and then “page” through them. The following strings can be added to the end of a search query:

  • &rows=2 this example returns a maximum of two “rows” or two query results.
  • &start=4 this example skips the first 4 available results

A query that combines skipping results and limiting the number of returned results looks like this:

1 http://localhost:8983/solr/select?q=British+One&wt=json&start=2&rows=2

Common Lisp Solr Client for Adding Documents

In the last example we relied on adding example documents to the Solr search index using the directions for setting up a new Solr installation. In a real application, in addition to performing search requests for indexed documents you will need to add new documents from your Lisp applications. Using the Drakma we will see that it is very easy to add documents.

We need to construct a bit of XML containing new documents in the form:

1 <add>
2     <doc>
3         <field name="id">123456</field>
4         <field name="title">Fishing Season</field>
5     </doc>
6 </add>

You can specify whatever field names (attributes) that are required for your application. You can also pass multiple <doc></doc> elements in one add request. We will want to specify documents in a Lisp-like way: a list of cons values where each cons value is a field name and a value. For the last XML document example we would like an API that lets us just deal with Lisp data like:

1  (do-add '(("id" . "12345")
2            ("title" . "Fishing Season")))

One thing to note: the attribute names and values must be passed as strings. Other data types like integers, floating point numbers, structs, etc. will not work.

This is nicer than having to use XML, right? The first thing we need is a function to convert a list of cons values to XML. I could have used the XML Builder functionality in the cxml library that is available via Quicklisp, but for something this simple I just wrote it in pure Common Lisp with no other dependencies (also in the example file src/solr-client.lisp) :

 1 (defun keys-values-to-xml-string  (keys-values-list)
 2  (with-output-to-string (stream)
 3    (format stream "<add><doc>")
 4    (dolist (kv keys-values-list)
 5      (format stream "<field name=\"")
 6      (format stream (car kv))
 7      (format stream "\">")
 8      (format stream (cdr kv))
 9      (format stream "\"</field>"))
10    (format stream "</doc></add>")))

The macro with-output-to-string on line 2 of the listing is my favorite way to generate strings. Everything written to the variable stream inside the macro call is appended to a string; this string is the return value of the macro.

The following function adds documents to the Solr document input queue but does not actually index them:

1 (defun do-add (keys-values-list)
2   (drakma:http-request
3    "http://localhost:8983/solr/update"
4    :method :post
5    :content-type "application/xml"
6    :content ( keys-values-to-xml-string  keys-values-list)))

You have noticed in line 3 that I am accessing a Solr server running on localhost and not a remote server. In an application using a remote Solr server you would need to modify this to reference your server; for example:

1 "http://solr.knowledgebooks.com:8983/solr/update"

For efficiency Solr does not immediately add new documents to the index until you commit the additions. The following function should be called after you are done adding documents to actually add them to the index:

1 (defun commit-adds ()
2   (drakma:http-request
3    "http://localhost:8983/solr/update"
4    :method :post
5    :content-type "application/xml"
6    :content "<commit></commit>"))

Notice that all we need is an empty element <commit></commit> that signals the Solr server that it should index all recently added documents. The following repl listing shows everything working together (I am assuming that the contents of the file src/solr-client.lisp has been loaded); not all of the output is shown in this listing:

 1 * (do-add '(("id" . "12345") ("title" . "Fishing Season")))
 2 
 3 200
 4 ((:CONTENT-TYPE . "application/xml; charset=UTF-8")
 5  (:CONNECTION . "close"))
 6 #<PURI:URI http://localhost:8983/solr/update>
 7 #<FLEXI-STREAMS:FLEXI-IO-STREAM {1009193133}>
 8 T
 9 "OK"
10 * (commit-adds)
11 
12 200
13 ((:CONTENT-TYPE . "application/xml; charset=UTF-8")
14  (:CONNECTION . "close"))
15 #<PURI:URI http://localhost:8983/solr/update>
16 #<FLEXI-STREAMS:FLEXI-IO-STREAM {10031F20B3}>
17 T
18 "OK"
19 * (do-search "fishing")
20 
21 ((:RESPONSE-HEADER (:STATUS . 0) (:*Q-TIME . 2)
22   (:PARAMS (:Q . "fishing") (:WT . "json")))
23  (:RESPONSE (:NUM-FOUND . 1) (:START . 0)
24   (:DOCS
25    ((:ID . "12345\"") (:TITLE "Fishing Season\"")
26     (:--VERSION-- . 1440293991717273600)))))
27 *

Common Lisp Solr Client Wrap Up

Solr has a lot of useful features that we have not used here like supporting faceted search (drilling down in previous search results), geolocation search, and looking up indexed documents by attribute. In the examples I have shown you, all text fields are indexed but Solr optionally allows you fine control over indexing, spelling correction, word stemming, etc.

Solr is a very capable tool for storing, indexing, and searching data. I have seen Solr used effectively on projects as a replacement for a relational database or other NoSQL data stores like CouchDB or MongoDB. There is a higher overhead for modifying or removing data in Solr so for applications that involve frequent modifications to stored data Solr might not be a good choice.

NoSQL Wrapup

There are more convenient languages than Common Lisp to use for accessing MongoDB. To be honest, my favorites are Ruby and Clojure. That said, for applications where the advantages of Common Lisp are compelling, it is good to know that your Common Lisp applications can play nicely with MongoDB.

I am a polyglot programmer: I like to use the best programming language for any specific job. When we design and build systems with more than one programming language, there are several options to share data:

  • Use foreign function interfaces to call one language from another from inside one process.
  • Use a service architecture and send requests using REST or SOAP.
  • Use shared data stores, like relational databases, MongoDB, CouchDB and Solr.

Hopefully this chapter and the last chapter will provide most of what you need for the last option.