Hello, all lovers of common-lisp.
In this article I will tell you about my experience in implementing the common-lisp library of access to the
mongo object DBMS, which is called
mongo-cl-driver .
')
Having read on the Internet about how mongo-db is fast, scalable and cool, and having a far and very poor experience of interacting with this database on ++, I decided to try this database in my web-based project written in common-lisp. Having, however, some doubts about the correctness of the choice of the DBMS, I can call my experience a success, since the implemented functionality at least works.
Any person who starts programming access to the mongo database will somehow come across Internet links to cl-mongo — the first mongo database access provider that appeared on common-lisp. Using cl-mongo in my project, I came across a series of problems with data conversion to json, which began when it became necessary to translate the query results along the DBMS-> common-lisp-server-> javascript client. By the way for such encoding / decoding there are libraries known to me:
1)
yasson
2)
cl-json
Here are some examples of using mongo-cl-driver for common tasks of programming access to mongo in common-lisp. If the examples seem to the reader obscure, torn out of context, then there is an opportunity to see examples of use in accessible
source codes . The most valuable pieces of code are contained in files webserver.lisp and competitions.lisp
In general, take this bull by the horns:
Connecting and accessing the database can be divided into two steps: creating an instance of the database class and an instance of the collection class. Further, it is very convenient to work with the collection instance.
Create a CLOS instance of the database class. You can read about object-oriented common-lisp programming in the translation of the book practical-common-lisp. This constructor can also take a host, port, user, and password as arguments, however, the author used only the first two parameters.
Creating a database instance to access the database called ski73:
(defparameter *db-instance* (make-instance 'mongo:database :name "ski73"))
To work with a specific collection, you must create an instance of the collection class:
(defparameter *competitions* (mongo:collection *db-instance* "competitions"))
Now we can use it in our project.
To select information of interest, you can use one of the functions provided by mongo-cl-driver: find-one or find-list. For a start, it is proposed to submit what the request looks like in the mongo console for selecting the name and date of the competition in the competitions collection:
db.competitions.find({}, {title: 1, date: 1});
To select all fields of the document, you need to call find without parameters:
db.competitions.find();
As the first member of the find function, the sample conditions are passed. Similarly, we specify the where clause in the SQL query. The second parameter is the return value filter - we specify the target field names we are interested in, marking them with 1-kami. For more information, refer to
documentation on mongo requests .
In mong-cl-driver, the list of documents is retrieved via the find-list function, and a single document is retrieved via find-one. Unfortunately, neither method can (from the subjective experience of the author) support the query and filter parameters by default. Here is the list of competitions from the competitions collection.
(find-list *competitions* :query (son) :fields (son "title" 1 "date" 1))
In order to exclude any filters and search conditions from the selection, you need to pass as parameter values: query and: fields calls to the son function without parameters.
For example, like this:
(find-list *competitions* :query (son) :fields (son))
To find a single document, use the find-one function. As the first (filter) and second (mask) parameters, it also needs to be passed, if a request is made without conditions and filters, empty calls are son
For example:
(find-one *coll-instance* (son) (son))
What is a son?
In order for the find request in common-lisp to be executed in the same way as is done in the mongo console, it is necessary to pass in it two structures with a set of key-value values. By the way, in the same cl-mongo there is a similar function, which is called kv (key-value). I do not remember exactly, but there are some inconveniences with it, along with its other functions.
In mongo-cl-driver, there is a son function.
Here is an invented example of constructing a query with non-empty query and fields parameters:
(find-list *coll-instance* :query (son "name" "Ivan" "surname" "Ivanov") :fields (son "name" 1 "surname" 1 "address" 1 "telephone"))
At even places, the functions have target key (member names) at odd value.
The following is a simple example of a method for obtaining a key-value object from a class representing a sporting event.
(defmethod mongo-doc ((c-instance competition)) (son "title" (title c-instance) "date" (date c-instance) "begin-time" (begin-time c-instance) "end-time" (end-time c-instance) "captions" (captions c-instance) ))
Search by Id
A related task in the development of the client-server http application was the transfer to the server of the id received earlier - a previous request from the client. Perhaps the approach is not entirely correct, but I decided that the id should be transferred as a single piece. The following example will help clarify the situation.
Imagine that the database stores the objects of the competition and each has nested lists of races (rounds)
This is how the list of competitions is formed to be sent to the client in the form of json. The response is generated in the context of the hunchentoot server http manager . define-url-fn is a macro that creates and registers a handler function for a competition-list request, for example, ski73.ru : 4242 / competitons-list. Convenient, right?
In this example, we register a request handler for a competition list. We are interested in the name and date. find-list will always return for each found document its 12-byte id-address along with the fields specified in the: fields parameter. On the client-side javascript side, it is stored as an array of 12 numbers. It was decided to serialize it into a line to transfer another request to the server, which would receive a more capacious structure with information on races with specific results of athletes.
The mongoId function is designed to convert an array of numbers into a string made up of identifier bytes in accordance with the contents of the 12-element mid array.
function mongoId(mid) { var strId = new String(); var plusByCode = function(el, index, arr) { return strId += String.fromCharCode(el); }; mid.raw.forEach(plusByCode); return strId; }
We put the string obtained with mongoId on the POST request c. And, further, on the server side, information is requested on this id from mongo-db. This is done like this:
; (define-url-fn (competition-info) (let ((id (post-parameter "id")) ) (str (encode-json-to-string (find-one *competitions* (son "_id" (make-instance 'object-id :raw (flexi-streams:string-to-octets id))) (son "rounds" 1 "captions" 1 "title" 1))) )))
A key place in constructing a query to a DBMS is the function of obtaining a 12-element vector of numbers from a string transmitted from the client as a POST parameter - flexi-streams: string-to-octets. The resulting vector is placed in the object-id class constructor.
And finally, for those who also want to try to have fewer curses in the direction of the author, there is a very brief description of installing mongo-cl-driver in the common-lisp environment. Practically, the sbcl implementation was used.
When installing mongo-cl-driver, beginner lisp-programmers, to whom the author himself also refers, may have seemingly intractable problems.
Install mongo-cl-driver with this simple spell:
(ql: quickload "mongo-cl-driver")
In your Lisp environment, by this time, quick-lisp should be configured - a system for downloading and installing lisp programs. On the official website of the project, you can read how to put it and poyuzat - www.quicklisp.org/beta
Most likely, an exception (restart) will appear in the middle of the installation process, an error message stating that there is an unresolved dependency - cl-camel-case.
We take it from the author mongo-cl-driver and install it using one of the three methods described on the wonderful site lisper.ru (with a beautiful lizard), in the wiki section
Hope nothing is missing. I will add only that the library was used when creating the author's resource for skiers. Source available
Special thanks to the author mongo-cl-driver archimag for some tips during the development of the resource. And good luck, friends!