Mariusz Prowaźnik

o programowaniu w Javie, Scali i Clojure.


Clojure i baza danych

W ramach nauki Clojure napisałem krótki programik do zapisywania i odczytywania danych przy użyciu clojure.java.jdbc. Zamieszczam jako szybki przykład.

Przechowywanymi danymi będą notowania giełdowe, a jako bazy użyję HSQLDB. Tabela stock będzie zawierać symbol papieru wartościowego, jego opis, oraz nazwę tabeli z notowaniami (notowania poszczególnych papierów w oddzielnych tabelach).

stock
(pk) ticker
table_name
description
quot_eod_kghm
(pk) date
open
close
high
low
volume
quot_eod_alma
(pk) date
open
close
high
low
volume
quot_eod_pekao
(pk) date
open
close
high
low
volume
itd...

Funkcjonalności, które można by sobie życzyć od takiego kodu:

  1. Zainicjowanie tabel
  2. Dodanie papieru wartościowego
  3. Pobranie symboli wszystkich papierów wartościowych
  4. Usunięcie papieru wartościowego z notowaniami
  5. Dodanie notowania
  6. Pobranie notowań

Realizacja:

Do budowania projektu użyję Maven'a, fragment pom.xml:

<dependencies>
 <dependency>
  <groupId>org.clojure</groupId>
  <artifactId>clojure</artifactId>
  <version>1.4.0</version>
 </dependency>
 <dependency>
  <groupId>hsqldb</groupId>
  <artifactId>hsqldb</artifactId>
  <version>1.8.0.10</version>
 </dependency>
 <dependency>
  <groupId>clj-time</groupId>
  <artifactId>clj-time</artifactId>
  <version>0.4.4</version>
 </dependency>
 <dependency>
  <groupId>org.clojure</groupId>
  <artifactId>java.jdbc</artifactId>
  <version>0.2.3</version>
 </dependency>
</dependencies>
<repositories>
 <repository>
  <id>clojars.org</id>
  <url>http://clojars.org/repo</url>
 </repository>
</repositories>

Zależność clj-time dodałem, żeby ułatwić sobie parsowanie i formatowanie dat.

sqlexample.clj:

(ns sqlexample
  (:require [clojure.java.jdbc :as sql]
            [clj-time.core :as jt]
            [clj-time.format :as cf]))

;;Mapa z parametrami połączenia do bazy danych. 
;;HSQLDB będzie zapisywać stan bazy w pliku db/stocks.script
;;Plik ten można podejrzeć w notatniku, zawiera SQL.
(def db { 
         :subprotocol "hsqldb" 
         :user "SA"
         :password ""
         :subname "db/stocks;create=true"})

(def table-prefix "quot_eod_") ;prefix nazwy tabeli z notowaniem
(def date-formatter (cf/formatter "yyyyMMdd")) ;do formatowania i parsowania dat

(defn init-stock-table 
"Tworzy tabelę stock. Wykorzystuje makro with-connection do otwarcia połączenia do bazy danych, 
oraz makro create-table."
  []
  (sql/with-connection db
    (sql/create-table :stock
       [:ticker "VARCHAR(32)" "PRIMARY KEY"]
       [:table_name "VARCHAR(64)"]
       [:description "VARCHAR(256)"])))

(defn- init-quot-table
"Tworzy tabelę z notowaniami. Nie otwiera połączenia, będzie wywoływana wewnątrz transakcji."
  [ticker]
  (sql/create-table (str table-prefix ticker)
                    [:date "DATE" "PRIMARY KEY"]
                    [:open "DOUBLE"]
                    [:close "DOUBLE"]
                    [:high "DOUBLE"]
                    [:low "DOUBLE"]
                    [:volume "DOUBLE"]))

(defn add-ticker 
"Dodanie papieru wartościowego. W tym celu musi być dodany rekord to tabeli stock, 
oraz stworzona tabela z notowaniami. Jeśli któraś z operacji się nie powiedzie, 
druga musi zostać wycofana, dlatego opakowane są w transakcję "
  [ticker] 
  (sql/with-connection 
    db
    (sql/transaction
      (sql/insert-record
        :stock
        {:ticker ticker :table_name (str table-prefix ticker)}
        )
      (init-quot-table ticker)
      )))

(defn get-tickers 
  "Pobiera symbole wszystkich papierów wartościowych"
  []
  (sql/with-connection
    db
    (sql/with-query-results 
      res
      ["select ticker from stock"]
      (map :ticker (into [] res)))))

(defn add-quotation
 "Dodaje notowanie."
 [ticker quotation]
  (sql/with-connection
    db
    (sql/insert-record
      (str table-prefix ticker)
      quotation)))
    

(defn get-quotations
 "Pobiera notowania."
 [ticker]
  (sql/with-connection
    db
    (sql/with-query-results 
      res
      [(str "select * from " table-prefix ticker " order by date asc")]
      (into [] res))))

(defn delete-ticker
 "Usuwa dane papieru wartościowego."
 [ticker]
  (sql/with-connection
    db
    (sql/transaction
      (sql/drop-table 
        (str table-prefix ticker))
      (sql/delete-rows
        :stock
        ["ticker=?" ticker])
      )))

Użycie

(init-stock-table)
;(0)

(get-tickers)
;()

(add-ticker "kghm")
;(0)

(get-tickers)
;("kghm")

(get-quotations "kghm")
;[]

(add-quotation "kghm" 
{:open 1.0 :close 2.0 :high 3.0 :low 0.50 :volume 100 :date (jt/date-time 2012 12 22)})
;1

(get-quotations "kghm")
;[{:volume 100.0, :low 0.5, :high 3.0, :close 2.0, :open 1.0, 
;:date #inst "2012-12-21T23:00:00.000-00:00"}]

Co dalej?

Warto zapoznać się z dwoma językami dziedzinowymi (ang. domain specyfic language) na bazie Clojure: