CLSQL - Part 2
Tuesday, June 8, 2004
In my previous post, I discussed some of the advantages of CLSQL and how to install it. In this posting, I'll walk through an interactive, annotated session using CLSQL in order to illustrate some of the characteristics of working with CLSQL. My example will use a Sales Order header file (typically containing the customer and generic order information) and a Sales Order detail file (typically containing the individual items that are being purchased). There is a 1:n relationship between the header and the detail files and the columns in the tables are kept to a minimum for the purposes of this walk-through in order to keep things simple.
- I'll start off by connecting to my "test" database. For this example, I'll be using MySQL as the database back-end. However, this example has been tested with SQLite as well and should work with any of the databases supported by CLSQL.
CL-USER> (use-package :clsql-user)
T
CL-USER> (connect `("" "test" "" "") :database-type :mysql)
#<CLSQL-MYSQL:MYSQL-DATABASE /test/ OPEN @ #x20a6b1da>
CL-USER> (start-sql-recording) ; No value CL-USER> (enable-sql-reader-syntax) ; No value
CL-USER> (execute-command "create table `header` (`id` int primary key,
`customer` varchar(50),
`comments` varchar(50))")
CL-USER> (create-table [header]
'(([id] integer primary key)
([customer] (string 50))
([comments] (string 50))))
;; 2004-06-08 20:18:01 /test/ => CREATE TABLE HEADER (ID INT PRIMARY KEY, CUSTOMER CHAR(50),
COMMENTS CHAR(50)) Type=InnoDB
; No value
CL-USER> (create-table [detail]
'(([id] integer)
([item] (string 25))
([quantity] integer)))
;; 2004-06-08 20:18:13 /test/ => CREATE TABLE DETAIL (ID INT, ITEM CHAR(25),
QUANTITY INT) Type=InnoDB
; No value
CL-USER> (execute-command "insert into header (id, customer) values (1, 'Joe Bloggs')")
;; 2004-06-08 20:18:18 /test/ => insert into header (id, customer)
values (1, 'Joe Bloggs')
; No value
CL-USER> (with-transaction () (insert-records :into [header] :attributes '([id] [customer]) :values '(2 "Mary Smith")) (insert-records :into [detail] :attributes '([id] [item] [quantity]) :values '(2 "BMW" 3))) ;; 2004-06-08 20:18:42 /test/ => BEGIN ;; 2004-06-08 20:18:42 /test/ => INSERT INTO HEADER (ID,CUSTOMER) VALUES (2,'Mary Smith') ;; 2004-06-08 20:18:42 /test/ => INSERT INTO DETAIL (ID,ITEM,QUANTITY) VALUES (2,'BMW',3) ;; 2004-06-08 20:18:42 /test/ => COMMIT NIL
CL-USER> (select [customer] [item] [quantity] :from '([header] [detail])
:where [= [header id] [detail id]])
;; 2004-06-08 20:18:50 /test/ => SELECT CUSTOMER,ITEM,QUANTITY FROM HEADER,DETAIL
WHERE (HEADER.ID = DETAIL.ID)
(("Mary Smith" "BMW" 3))
("CUSTOMER" "ITEM" "QUANTITY")
CL-USER> (def-view-class header () ((id :type integer :db-kind :key) (customer :type (string 50)) (comments :type (string 50)))) #<CLSQL-SYS::STANDARD-DB-CLASS HEADER> CL-USER> (def-view-class so-detail () ((id :db-kind :key :column id :type integer :initarg :id) (item :column item :type (string 25) :initarg :item) (quantity :column quantity :type integer :initarg :quantity)) (:base-table detail)) #<CLSQL-SYS::STANDARD-DB-CLASS SO-DETAIL>
CL-USER> (def-view-class expanded-order-line (so-detail) ((header-info :db-kind :join :db-info (:home-key id :foreign-key id :join-class header))) (:base-table detail)) #CLSQL-SYS::STANDARD-DB-CLASS EXPANDED-ORDER-LINE>
CL-USER> (select 'expanded-order-line) ;; 2004-06-08 20:19:59 /test/ => SELECT DETAIL.QUANTITY,DETAIL.ITEM,DETAIL.ID FROM DETAIL ((#<EXPANDED-ORDER-LINE @ #x207d60ca>)) CL-USER> (describe (caar *)) #<EXPANDED-ORDER-LINE @ #x207d60ca> is an instance of #<CLSQL-SYS::STANDARD-DB-CLASS EXPANDED-ORDER-LINE>: The following slots have :INSTANCE allocation: HEADER-INFO <unbound> ID 2 ITEM "BMW" QUANTITY 3 VIEW-DATABASE #<CLSQL-MYSQL:MYSQL-DATABASE /test/ OPEN @ #x2076097a> ; No value
CL-USER> (setf bmw-order-detail
(car
(select 'expanded-order-line
:flatp t
:where [= [slot-value 'expanded-order-line 'item] "BMW"])))
;; 2004-06-08 20:20:19 /test/ => SELECT DETAIL.QUANTITY,DETAIL.ITEM,DETAIL.ID
FROM DETAIL WHERE (DETAIL.ITEM = 'BMW')
#<EXPANDED-ORDER-LINE @ #x2080eef2>
CL-USER> (slot-value (slot-value bmw-order-detail 'header-info) 'customer)
;; 2004-06-08 20:20:23 /test/ => SELECT HEADER.COMMENTS,HEADER.CUSTOMER,HEADER.ID
FROM HEADER WHERE (HEADER.ID = 2)
"Mary Smith"
CL-USER> (setq new-line (make-instance 'so-detail
:id 2
:item "VW"
:quantity 1))
#<SO-DETAIL @ #x2082286a>
CL-USER> (update-records-from-instance new-line)
;; 2004-06-08 20:20:39 /test/ => INSERT INTO DETAIL (ID,ITEM,QUANTITY) VALUES (2,'VW',1)
; No value
CL-USER> (select [customer] [item] [quantity] :from '([header] [detail])
:where [= [header id] [detail id]])
;; 2004-06-08 20:20:51 /test/ => SELECT CUSTOMER,ITEM,QUANTITY FROM HEADER,DETAIL
WHERE (HEADER.ID = DETAIL.ID)
(("Mary Smith" "BMW" 3) ("Mary Smith" "VW" 1))
("CUSTOMER" "ITEM" "QUANTITY")
CL-USER> (disconnect) T CL-USER>If you would like to see some more examples of how to use CLSQL, the CLSQL tutorial is quite useful. Also, the tremendous CommonSQL tutorial that Nick Levine gave at ILC2002 is an excellent introduction to CLSQL. Even though his tutorial is targetted towards Xanalys CommonSQL, the examples work equally well in CLSQL (with the occasional minor tweak).
In summary, the CLSQL package is quite easy to work with and allows you to use a mixture of functional and OO approaches in working with relational data. It provides high-performing native access to the underlying databases (as well as ODBC access as either an alternative or as a means to access databases that are not supported with a native API). Kevin Rosenberg (and contributors) are to be commended for an excellent package!

